@jwiedeman/gtm-kit-react 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,276 @@
1
+ # @react-gtm-kit/react-modern
2
+
3
+ [![CI](https://github.com/jwiedeman/react-gtm-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/jwiedeman/react-gtm-kit/actions/workflows/ci.yml)
4
+ [![Coverage](https://codecov.io/gh/jwiedeman/react-gtm-kit/graph/badge.svg?flag=react-modern)](https://codecov.io/gh/jwiedeman/react-gtm-kit)
5
+ [![npm version](https://img.shields.io/npm/v/@react-gtm-kit/react-modern.svg)](https://www.npmjs.com/package/@react-gtm-kit/react-modern)
6
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@react-gtm-kit/react-modern)](https://bundlephobia.com/package/@react-gtm-kit/react-modern)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![React](https://img.shields.io/badge/React-16.8+-61DAFB.svg?logo=react)](https://reactjs.org/)
10
+
11
+ **React hooks and provider for Google Tag Manager. StrictMode-safe. Zero double-fires.**
12
+
13
+ The modern React adapter for GTM Kit - uses hooks and Context API for clean, idiomatic React code.
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @react-gtm-kit/core @react-gtm-kit/react-modern
21
+ ```
22
+
23
+ ```bash
24
+ yarn add @react-gtm-kit/core @react-gtm-kit/react-modern
25
+ ```
26
+
27
+ ```bash
28
+ pnpm add @react-gtm-kit/core @react-gtm-kit/react-modern
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ### Step 1: Wrap Your App
36
+
37
+ ```tsx
38
+ // App.tsx or index.tsx
39
+ import { GtmProvider } from '@react-gtm-kit/react-modern';
40
+
41
+ function App() {
42
+ return (
43
+ <GtmProvider config={{ containers: 'GTM-XXXXXX' }}>
44
+ <YourApp />
45
+ </GtmProvider>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ### Step 2: Push Events
51
+
52
+ ```tsx
53
+ import { useGtmPush } from '@react-gtm-kit/react-modern';
54
+
55
+ function BuyButton() {
56
+ const push = useGtmPush();
57
+
58
+ const handleClick = () => {
59
+ push({ event: 'purchase', value: 49.99 });
60
+ };
61
+
62
+ return <button onClick={handleClick}>Buy Now</button>;
63
+ }
64
+ ```
65
+
66
+ **That's it!** GTM is now running.
67
+
68
+ ---
69
+
70
+ ## Features
71
+
72
+ | Feature | Description |
73
+ | ------------------- | ----------------------------------------- |
74
+ | **StrictMode-Safe** | No double-fires in React development mode |
75
+ | **Hooks-Based** | Modern React patterns with Context API |
76
+ | **React 16.8+** | Works with any modern React version |
77
+ | **TypeScript** | Full type definitions included |
78
+ | **Consent Mode v2** | Built-in GDPR compliance hooks |
79
+ | **SSR Compatible** | Safe for Next.js, Remix, etc. |
80
+
81
+ ---
82
+
83
+ ## Available Hooks
84
+
85
+ ### `useGtmPush()`
86
+
87
+ Get a push function to send events to GTM.
88
+
89
+ ```tsx
90
+ const push = useGtmPush();
91
+
92
+ push({ event: 'button_click', button_id: 'cta-main' });
93
+ ```
94
+
95
+ ### `useGtm()`
96
+
97
+ Get the full GTM context with all methods.
98
+
99
+ ```tsx
100
+ const { client, push, updateConsent, setConsentDefaults } = useGtm();
101
+ ```
102
+
103
+ ### `useGtmConsent()`
104
+
105
+ Manage consent state.
106
+
107
+ ```tsx
108
+ const { updateConsent, setConsentDefaults } = useGtmConsent();
109
+
110
+ // After user accepts cookies
111
+ updateConsent({
112
+ ad_storage: 'granted',
113
+ analytics_storage: 'granted'
114
+ });
115
+ ```
116
+
117
+ ### `useGtmClient()`
118
+
119
+ Get the raw GTM client instance.
120
+
121
+ ```tsx
122
+ const client = useGtmClient();
123
+ ```
124
+
125
+ ### `useGtmReady()`
126
+
127
+ Get a function that resolves when GTM is fully loaded.
128
+
129
+ ```tsx
130
+ const whenReady = useGtmReady();
131
+
132
+ useEffect(() => {
133
+ whenReady().then(() => {
134
+ console.log('GTM is ready!');
135
+ });
136
+ }, [whenReady]);
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Consent Mode v2 (GDPR)
142
+
143
+ ```tsx
144
+ import { GtmProvider, useGtmConsent } from '@react-gtm-kit/react-modern';
145
+ import { consentPresets } from '@react-gtm-kit/core';
146
+
147
+ // Set defaults BEFORE GTM loads
148
+ <GtmProvider
149
+ config={{ containers: 'GTM-XXXXXX' }}
150
+ onBeforeInit={(client) => {
151
+ client.setConsentDefaults(consentPresets.eeaDefault, { region: ['EEA'] });
152
+ }}
153
+ >
154
+ <App />
155
+ </GtmProvider>;
156
+
157
+ // In your cookie banner
158
+ function CookieBanner() {
159
+ const { updateConsent } = useGtmConsent();
160
+
161
+ // Accept all tracking
162
+ const acceptAll = () => updateConsent(consentPresets.allGranted);
163
+
164
+ // Reject all tracking
165
+ const rejectAll = () => updateConsent(consentPresets.eeaDefault);
166
+
167
+ // Analytics only (mixed consent)
168
+ const analyticsOnly = () => updateConsent(consentPresets.analyticsOnly);
169
+
170
+ // Granular: update specific categories
171
+ const customChoice = () =>
172
+ updateConsent({
173
+ analytics_storage: 'granted',
174
+ ad_storage: 'denied',
175
+ ad_user_data: 'denied',
176
+ ad_personalization: 'denied'
177
+ });
178
+
179
+ return (
180
+ <div>
181
+ <button onClick={acceptAll}>Accept All</button>
182
+ <button onClick={rejectAll}>Reject All</button>
183
+ <button onClick={analyticsOnly}>Analytics Only</button>
184
+ </div>
185
+ );
186
+ }
187
+ ```
188
+
189
+ **Partial Updates** - Only update what changed:
190
+
191
+ ```tsx
192
+ // User later opts into ads from preference center
193
+ updateConsent({ ad_storage: 'granted', ad_user_data: 'granted' });
194
+ // Other categories (analytics_storage, ad_personalization) unchanged
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Provider Options
200
+
201
+ ```tsx
202
+ <GtmProvider
203
+ config={{
204
+ containers: 'GTM-XXXXXX', // Required
205
+ dataLayerName: 'dataLayer', // Optional
206
+ host: 'https://custom.host.com', // Optional
207
+ scriptAttributes: { nonce: '...' } // Optional: CSP
208
+ }}
209
+ onBeforeInit={(client) => {
210
+ // Called before GTM initializes
211
+ // Perfect for consent defaults
212
+ }}
213
+ onAfterInit={(client) => {
214
+ // Called after GTM initializes
215
+ }}
216
+ >
217
+ {children}
218
+ </GtmProvider>
219
+ ```
220
+
221
+ ---
222
+
223
+ ## React Router Integration
224
+
225
+ ```tsx
226
+ import { useLocation } from 'react-router-dom';
227
+ import { useGtmPush } from '@react-gtm-kit/react-modern';
228
+ import { useEffect, useRef } from 'react';
229
+
230
+ function PageTracker() {
231
+ const location = useLocation();
232
+ const push = useGtmPush();
233
+ const lastPath = useRef('');
234
+
235
+ useEffect(() => {
236
+ const path = location.pathname + location.search;
237
+ if (path !== lastPath.current) {
238
+ lastPath.current = path;
239
+ push({ event: 'page_view', page_path: path });
240
+ }
241
+ }, [location, push]);
242
+
243
+ return null;
244
+ }
245
+
246
+ // Add to your app
247
+ function App() {
248
+ return (
249
+ <GtmProvider config={{ containers: 'GTM-XXXXXX' }}>
250
+ <BrowserRouter>
251
+ <PageTracker />
252
+ <Routes>...</Routes>
253
+ </BrowserRouter>
254
+ </GtmProvider>
255
+ );
256
+ }
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Why StrictMode-Safe Matters
262
+
263
+ In React development mode with StrictMode, components mount twice. This causes most GTM libraries to fire events twice. **GTM Kit handles this automatically** - you get exactly one initialization and no duplicate events.
264
+
265
+ ---
266
+
267
+ ## Requirements
268
+
269
+ - React 16.8+ (hooks support)
270
+ - `@react-gtm-kit/core` (peer dependency)
271
+
272
+ ---
273
+
274
+ ## License
275
+
276
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var gtmKit = require('@jwiedeman/gtm-kit');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ var u=react.createContext(null),d=(t,n)=>{process.env.NODE_ENV!=="production"&&t!==n&&console.warn("[react-gtm-kit] GtmProvider received new configuration; reconfiguration after mount is not supported. The initial configuration will continue to be used.");},G=t=>{let n=react.useRef(),e=react.useRef();return n.current?e.current&&d(e.current,t):(n.current=gtmKit.createGtmClient(t),e.current=t),n.current},f=({config:t,children:n})=>{let e=G(t);react.useEffect(()=>(e.init(),()=>{e.teardown();}),[e]);let p=react.useMemo(()=>({client:e,push:o=>e.push(o),setConsentDefaults:(o,r)=>e.setConsentDefaults(o,r),updateConsent:(o,r)=>e.updateConsent(o,r),whenReady:()=>e.whenReady(),onReady:o=>e.onReady(o)}),[e]);return jsxRuntime.jsx(u.Provider,{value:p,children:n})},s=()=>{let t=react.useContext(u);if(!t)throw new Error("useGtm hook must be used within a GtmProvider instance.");return t},v=s,x=()=>s().client,y=()=>s().push,h=()=>{let{setConsentDefaults:t,updateConsent:n}=s();return react.useMemo(()=>({setConsentDefaults:t,updateConsent:n}),[t,n])},P=()=>{let{whenReady:t}=s();return t};
8
+
9
+ exports.GtmProvider = f;
10
+ exports.useGtm = v;
11
+ exports.useGtmClient = x;
12
+ exports.useGtmConsent = h;
13
+ exports.useGtmPush = y;
14
+ exports.useGtmReady = P;
15
+ //# sourceMappingURL=out.js.map
16
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.tsx"],"names":["createContext","useContext","useEffect","useMemo","useRef","createGtmClient","jsx","GtmContext","warnOnConfigChange","initialConfig","nextConfig","useStableClient","config","clientRef","configRef","GtmProvider","children","client","value","state","options","callback","useGtmContext","context","useGtm","useGtmClient","useGtmPush","useGtmConsent","setConsentDefaults","updateConsent","useGtmReady","whenReady"],"mappings":"AAAA,OAAS,iBAAAA,EAAe,cAAAC,EAAY,aAAAC,EAAW,WAAAC,EAAS,UAAAC,MAAgD,QACxG,OACE,mBAAAC,MAOK,qBAmEE,cAAAC,MAAA,oBA/CT,IAAMC,EAAaP,EAAsC,IAAI,EAEvDQ,EAAqB,CAACC,EAAuCC,IAA6C,CAC1G,QAAQ,IAAI,WAAa,cAAgBD,IAAkBC,GAC7D,QAAQ,KACN,2JAEF,CAEJ,EAEMC,EAAmBC,GAA8C,CACrE,IAAMC,EAAYT,EAAkB,EAC9BU,EAAYV,EAA+B,EAEjD,OAAKS,EAAU,QAGJC,EAAU,SACnBN,EAAmBM,EAAU,QAASF,CAAM,GAH5CC,EAAU,QAAUR,EAAgBO,CAAM,EAC1CE,EAAU,QAAUF,GAKfC,EAAU,OACnB,EAEaE,EAAc,CAAC,CAAE,OAAAH,EAAQ,SAAAI,CAAS,IAAqC,CAClF,IAAMC,EAASN,EAAgBC,CAAM,EAErCV,EAAU,KACRe,EAAO,KAAK,EACL,IAAM,CACXA,EAAO,SAAS,CAClB,GACC,CAACA,CAAM,CAAC,EAEX,IAAMC,EAAQf,EACZ,KAAO,CACL,OAAAc,EACA,KAAOC,GAAUD,EAAO,KAAKC,CAAK,EAClC,mBAAoB,CAACC,EAAOC,IAAYH,EAAO,mBAAmBE,EAAOC,CAAO,EAChF,cAAe,CAACD,EAAOC,IAAYH,EAAO,cAAcE,EAAOC,CAAO,EACtE,UAAW,IAAMH,EAAO,UAAU,EAClC,QAAUI,GAAaJ,EAAO,QAAQI,CAAQ,CAChD,GACA,CAACJ,CAAM,CACT,EAEA,OAAOX,EAACC,EAAW,SAAX,CAAoB,MAAOW,EAAQ,SAAAF,EAAS,CACtD,EAEMM,EAAgB,IAAuB,CAC3C,IAAMC,EAAUtB,EAAWM,CAAU,EACrC,GAAI,CAACgB,EACH,MAAM,IAAI,MAAM,yDAAyD,EAE3E,OAAOA,CACT,EAEaC,EAASF,EAETG,EAAe,IACnBH,EAAc,EAAE,OAGZI,EAAa,IACjBJ,EAAc,EAAE,KAGZK,EAAgB,IAAqB,CAChD,GAAM,CAAE,mBAAAC,EAAoB,cAAAC,CAAc,EAAIP,EAAc,EAC5D,OAAOnB,EAAQ,KAAO,CAAE,mBAAAyB,EAAoB,cAAAC,CAAc,GAAI,CAACD,EAAoBC,CAAa,CAAC,CACnG,EAEaC,EAAc,IAA0C,CACnE,GAAM,CAAE,UAAAC,CAAU,EAAIT,EAAc,EACpC,OAAOS,CACT","sourcesContent":["import { createContext, useContext, useEffect, useMemo, useRef, type PropsWithChildren, type JSX } from 'react';\nimport {\n createGtmClient,\n type ConsentRegionOptions,\n type ConsentState,\n type CreateGtmClientOptions,\n type DataLayerValue,\n type GtmClient,\n type ScriptLoadState\n} from '@jwiedeman/gtm-kit';\n\nexport interface GtmProviderProps extends PropsWithChildren {\n config: CreateGtmClientOptions;\n}\n\nexport interface GtmContextValue {\n client: GtmClient;\n push: (value: DataLayerValue) => void;\n setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;\n updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;\n whenReady: () => Promise<ScriptLoadState[]>;\n onReady: (callback: (state: ScriptLoadState[]) => void) => () => void;\n}\n\nexport interface GtmConsentApi {\n setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;\n updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;\n}\n\nconst GtmContext = createContext<GtmContextValue | null>(null);\n\nconst warnOnConfigChange = (initialConfig: CreateGtmClientOptions, nextConfig: CreateGtmClientOptions): void => {\n if (process.env.NODE_ENV !== 'production' && initialConfig !== nextConfig) {\n console.warn(\n '[react-gtm-kit] GtmProvider received new configuration; reconfiguration after mount is not supported. ' +\n 'The initial configuration will continue to be used.'\n );\n }\n};\n\nconst useStableClient = (config: CreateGtmClientOptions): GtmClient => {\n const clientRef = useRef<GtmClient>();\n const configRef = useRef<CreateGtmClientOptions>();\n\n if (!clientRef.current) {\n clientRef.current = createGtmClient(config);\n configRef.current = config;\n } else if (configRef.current) {\n warnOnConfigChange(configRef.current, config);\n }\n\n return clientRef.current!;\n};\n\nexport const GtmProvider = ({ config, children }: GtmProviderProps): JSX.Element => {\n const client = useStableClient(config);\n\n useEffect(() => {\n client.init();\n return () => {\n client.teardown();\n };\n }, [client]);\n\n const value = useMemo<GtmContextValue>(\n () => ({\n client,\n push: (value) => client.push(value),\n setConsentDefaults: (state, options) => client.setConsentDefaults(state, options),\n updateConsent: (state, options) => client.updateConsent(state, options),\n whenReady: () => client.whenReady(),\n onReady: (callback) => client.onReady(callback)\n }),\n [client]\n );\n\n return <GtmContext.Provider value={value}>{children}</GtmContext.Provider>;\n};\n\nconst useGtmContext = (): GtmContextValue => {\n const context = useContext(GtmContext);\n if (!context) {\n throw new Error('useGtm hook must be used within a GtmProvider instance.');\n }\n return context;\n};\n\nexport const useGtm = useGtmContext;\n\nexport const useGtmClient = (): GtmClient => {\n return useGtmContext().client;\n};\n\nexport const useGtmPush = (): ((value: DataLayerValue) => void) => {\n return useGtmContext().push;\n};\n\nexport const useGtmConsent = (): GtmConsentApi => {\n const { setConsentDefaults, updateConsent } = useGtmContext();\n return useMemo(() => ({ setConsentDefaults, updateConsent }), [setConsentDefaults, updateConsent]);\n};\n\nexport const useGtmReady = (): (() => Promise<ScriptLoadState[]>) => {\n const { whenReady } = useGtmContext();\n return whenReady;\n};\n"]}
@@ -0,0 +1,26 @@
1
+ import { PropsWithChildren, JSX } from 'react';
2
+ import { CreateGtmClientOptions, GtmClient, DataLayerValue, ConsentState, ConsentRegionOptions, ScriptLoadState } from '@jwiedeman/gtm-kit';
3
+
4
+ interface GtmProviderProps extends PropsWithChildren {
5
+ config: CreateGtmClientOptions;
6
+ }
7
+ interface GtmContextValue {
8
+ client: GtmClient;
9
+ push: (value: DataLayerValue) => void;
10
+ setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;
11
+ updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;
12
+ whenReady: () => Promise<ScriptLoadState[]>;
13
+ onReady: (callback: (state: ScriptLoadState[]) => void) => () => void;
14
+ }
15
+ interface GtmConsentApi {
16
+ setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;
17
+ updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;
18
+ }
19
+ declare const GtmProvider: ({ config, children }: GtmProviderProps) => JSX.Element;
20
+ declare const useGtm: () => GtmContextValue;
21
+ declare const useGtmClient: () => GtmClient;
22
+ declare const useGtmPush: () => ((value: DataLayerValue) => void);
23
+ declare const useGtmConsent: () => GtmConsentApi;
24
+ declare const useGtmReady: () => (() => Promise<ScriptLoadState[]>);
25
+
26
+ export { GtmConsentApi, GtmContextValue, GtmProvider, GtmProviderProps, useGtm, useGtmClient, useGtmConsent, useGtmPush, useGtmReady };
@@ -0,0 +1,26 @@
1
+ import { PropsWithChildren, JSX } from 'react';
2
+ import { CreateGtmClientOptions, GtmClient, DataLayerValue, ConsentState, ConsentRegionOptions, ScriptLoadState } from '@jwiedeman/gtm-kit';
3
+
4
+ interface GtmProviderProps extends PropsWithChildren {
5
+ config: CreateGtmClientOptions;
6
+ }
7
+ interface GtmContextValue {
8
+ client: GtmClient;
9
+ push: (value: DataLayerValue) => void;
10
+ setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;
11
+ updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;
12
+ whenReady: () => Promise<ScriptLoadState[]>;
13
+ onReady: (callback: (state: ScriptLoadState[]) => void) => () => void;
14
+ }
15
+ interface GtmConsentApi {
16
+ setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;
17
+ updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;
18
+ }
19
+ declare const GtmProvider: ({ config, children }: GtmProviderProps) => JSX.Element;
20
+ declare const useGtm: () => GtmContextValue;
21
+ declare const useGtmClient: () => GtmClient;
22
+ declare const useGtmPush: () => ((value: DataLayerValue) => void);
23
+ declare const useGtmConsent: () => GtmConsentApi;
24
+ declare const useGtmReady: () => (() => Promise<ScriptLoadState[]>);
25
+
26
+ export { GtmConsentApi, GtmContextValue, GtmProvider, GtmProviderProps, useGtm, useGtmClient, useGtmConsent, useGtmPush, useGtmReady };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { createContext, useEffect, useMemo, useRef, useContext } from 'react';
2
+ import { createGtmClient } from '@jwiedeman/gtm-kit';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var u=createContext(null),d=(t,n)=>{process.env.NODE_ENV!=="production"&&t!==n&&console.warn("[react-gtm-kit] GtmProvider received new configuration; reconfiguration after mount is not supported. The initial configuration will continue to be used.");},G=t=>{let n=useRef(),e=useRef();return n.current?e.current&&d(e.current,t):(n.current=createGtmClient(t),e.current=t),n.current},f=({config:t,children:n})=>{let e=G(t);useEffect(()=>(e.init(),()=>{e.teardown();}),[e]);let p=useMemo(()=>({client:e,push:o=>e.push(o),setConsentDefaults:(o,r)=>e.setConsentDefaults(o,r),updateConsent:(o,r)=>e.updateConsent(o,r),whenReady:()=>e.whenReady(),onReady:o=>e.onReady(o)}),[e]);return jsx(u.Provider,{value:p,children:n})},s=()=>{let t=useContext(u);if(!t)throw new Error("useGtm hook must be used within a GtmProvider instance.");return t},v=s,x=()=>s().client,y=()=>s().push,h=()=>{let{setConsentDefaults:t,updateConsent:n}=s();return useMemo(()=>({setConsentDefaults:t,updateConsent:n}),[t,n])},P=()=>{let{whenReady:t}=s();return t};
6
+
7
+ export { f as GtmProvider, v as useGtm, x as useGtmClient, h as useGtmConsent, y as useGtmPush, P as useGtmReady };
8
+ //# sourceMappingURL=out.js.map
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.tsx"],"names":["createContext","useContext","useEffect","useMemo","useRef","createGtmClient","jsx","GtmContext","warnOnConfigChange","initialConfig","nextConfig","useStableClient","config","clientRef","configRef","GtmProvider","children","client","value","state","options","callback","useGtmContext","context","useGtm","useGtmClient","useGtmPush","useGtmConsent","setConsentDefaults","updateConsent","useGtmReady","whenReady"],"mappings":"AAAA,OAAS,iBAAAA,EAAe,cAAAC,EAAY,aAAAC,EAAW,WAAAC,EAAS,UAAAC,MAAgD,QACxG,OACE,mBAAAC,MAOK,qBAmEE,cAAAC,MAAA,oBA/CT,IAAMC,EAAaP,EAAsC,IAAI,EAEvDQ,EAAqB,CAACC,EAAuCC,IAA6C,CAC1G,QAAQ,IAAI,WAAa,cAAgBD,IAAkBC,GAC7D,QAAQ,KACN,2JAEF,CAEJ,EAEMC,EAAmBC,GAA8C,CACrE,IAAMC,EAAYT,EAAkB,EAC9BU,EAAYV,EAA+B,EAEjD,OAAKS,EAAU,QAGJC,EAAU,SACnBN,EAAmBM,EAAU,QAASF,CAAM,GAH5CC,EAAU,QAAUR,EAAgBO,CAAM,EAC1CE,EAAU,QAAUF,GAKfC,EAAU,OACnB,EAEaE,EAAc,CAAC,CAAE,OAAAH,EAAQ,SAAAI,CAAS,IAAqC,CAClF,IAAMC,EAASN,EAAgBC,CAAM,EAErCV,EAAU,KACRe,EAAO,KAAK,EACL,IAAM,CACXA,EAAO,SAAS,CAClB,GACC,CAACA,CAAM,CAAC,EAEX,IAAMC,EAAQf,EACZ,KAAO,CACL,OAAAc,EACA,KAAOC,GAAUD,EAAO,KAAKC,CAAK,EAClC,mBAAoB,CAACC,EAAOC,IAAYH,EAAO,mBAAmBE,EAAOC,CAAO,EAChF,cAAe,CAACD,EAAOC,IAAYH,EAAO,cAAcE,EAAOC,CAAO,EACtE,UAAW,IAAMH,EAAO,UAAU,EAClC,QAAUI,GAAaJ,EAAO,QAAQI,CAAQ,CAChD,GACA,CAACJ,CAAM,CACT,EAEA,OAAOX,EAACC,EAAW,SAAX,CAAoB,MAAOW,EAAQ,SAAAF,EAAS,CACtD,EAEMM,EAAgB,IAAuB,CAC3C,IAAMC,EAAUtB,EAAWM,CAAU,EACrC,GAAI,CAACgB,EACH,MAAM,IAAI,MAAM,yDAAyD,EAE3E,OAAOA,CACT,EAEaC,EAASF,EAETG,EAAe,IACnBH,EAAc,EAAE,OAGZI,EAAa,IACjBJ,EAAc,EAAE,KAGZK,EAAgB,IAAqB,CAChD,GAAM,CAAE,mBAAAC,EAAoB,cAAAC,CAAc,EAAIP,EAAc,EAC5D,OAAOnB,EAAQ,KAAO,CAAE,mBAAAyB,EAAoB,cAAAC,CAAc,GAAI,CAACD,EAAoBC,CAAa,CAAC,CACnG,EAEaC,EAAc,IAA0C,CACnE,GAAM,CAAE,UAAAC,CAAU,EAAIT,EAAc,EACpC,OAAOS,CACT","sourcesContent":["import { createContext, useContext, useEffect, useMemo, useRef, type PropsWithChildren, type JSX } from 'react';\nimport {\n createGtmClient,\n type ConsentRegionOptions,\n type ConsentState,\n type CreateGtmClientOptions,\n type DataLayerValue,\n type GtmClient,\n type ScriptLoadState\n} from '@jwiedeman/gtm-kit';\n\nexport interface GtmProviderProps extends PropsWithChildren {\n config: CreateGtmClientOptions;\n}\n\nexport interface GtmContextValue {\n client: GtmClient;\n push: (value: DataLayerValue) => void;\n setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;\n updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;\n whenReady: () => Promise<ScriptLoadState[]>;\n onReady: (callback: (state: ScriptLoadState[]) => void) => () => void;\n}\n\nexport interface GtmConsentApi {\n setConsentDefaults: (state: ConsentState, options?: ConsentRegionOptions) => void;\n updateConsent: (state: ConsentState, options?: ConsentRegionOptions) => void;\n}\n\nconst GtmContext = createContext<GtmContextValue | null>(null);\n\nconst warnOnConfigChange = (initialConfig: CreateGtmClientOptions, nextConfig: CreateGtmClientOptions): void => {\n if (process.env.NODE_ENV !== 'production' && initialConfig !== nextConfig) {\n console.warn(\n '[react-gtm-kit] GtmProvider received new configuration; reconfiguration after mount is not supported. ' +\n 'The initial configuration will continue to be used.'\n );\n }\n};\n\nconst useStableClient = (config: CreateGtmClientOptions): GtmClient => {\n const clientRef = useRef<GtmClient>();\n const configRef = useRef<CreateGtmClientOptions>();\n\n if (!clientRef.current) {\n clientRef.current = createGtmClient(config);\n configRef.current = config;\n } else if (configRef.current) {\n warnOnConfigChange(configRef.current, config);\n }\n\n return clientRef.current!;\n};\n\nexport const GtmProvider = ({ config, children }: GtmProviderProps): JSX.Element => {\n const client = useStableClient(config);\n\n useEffect(() => {\n client.init();\n return () => {\n client.teardown();\n };\n }, [client]);\n\n const value = useMemo<GtmContextValue>(\n () => ({\n client,\n push: (value) => client.push(value),\n setConsentDefaults: (state, options) => client.setConsentDefaults(state, options),\n updateConsent: (state, options) => client.updateConsent(state, options),\n whenReady: () => client.whenReady(),\n onReady: (callback) => client.onReady(callback)\n }),\n [client]\n );\n\n return <GtmContext.Provider value={value}>{children}</GtmContext.Provider>;\n};\n\nconst useGtmContext = (): GtmContextValue => {\n const context = useContext(GtmContext);\n if (!context) {\n throw new Error('useGtm hook must be used within a GtmProvider instance.');\n }\n return context;\n};\n\nexport const useGtm = useGtmContext;\n\nexport const useGtmClient = (): GtmClient => {\n return useGtmContext().client;\n};\n\nexport const useGtmPush = (): ((value: DataLayerValue) => void) => {\n return useGtmContext().push;\n};\n\nexport const useGtmConsent = (): GtmConsentApi => {\n const { setConsentDefaults, updateConsent } = useGtmContext();\n return useMemo(() => ({ setConsentDefaults, updateConsent }), [setConsentDefaults, updateConsent]);\n};\n\nexport const useGtmReady = (): (() => Promise<ScriptLoadState[]>) => {\n const { whenReady } = useGtmContext();\n return whenReady;\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@jwiedeman/gtm-kit-react",
3
+ "version": "1.0.1",
4
+ "description": "React hooks and provider for GTM Kit - Google Tag Manager integration.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jwiedeman/GTM-Kit.git",
8
+ "directory": "packages/react-modern"
9
+ },
10
+ "author": "jwiedeman",
11
+ "keywords": [
12
+ "gtm",
13
+ "google-tag-manager",
14
+ "react",
15
+ "hooks"
16
+ ],
17
+ "license": "MIT",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "type": "module",
22
+ "main": "dist/index.cjs",
23
+ "module": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "clean": "rm -rf dist",
38
+ "lint": "eslint --max-warnings=0 \"src/**/*.{ts,tsx}\"",
39
+ "test": "jest --config ./jest.config.cjs --runInBand",
40
+ "typecheck": "tsc --noEmit"
41
+ },
42
+ "dependencies": {
43
+ "@jwiedeman/gtm-kit": "^1.0.1"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@testing-library/jest-dom": "^6.4.2",
50
+ "@testing-library/react": "^14.2.1",
51
+ "@types/react": "^18.3.0",
52
+ "@types/react-dom": "^18.3.0",
53
+ "react": "^18.3.1",
54
+ "react-dom": "^18.3.1",
55
+ "react-router-dom": "^6.27.0",
56
+ "tslib": "^2.6.2"
57
+ }
58
+ }