react-event-tracking 1.0.3 → 1.0.5

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 CHANGED
@@ -6,6 +6,21 @@ A convenient React context for tracking analytics events.
6
6
  - **Nested Contexts**: Automatically merges parameters from parent providers.
7
7
  - **Zero Re-renders**: No need to wrap props in `useCallback`/`useMemo`.
8
8
 
9
+ ## Table of Contents
10
+
11
+ <!-- toc -->
12
+
13
+ - [Installation](#installation)
14
+ - [Quickstart](#quickstart)
15
+ - [Advanced Usage](#advanced-usage)
16
+ * [Multiple Trackers & Factory](#multiple-trackers--factory)
17
+ * [Filtering Events](#filtering-events)
18
+ * [Transforming Events](#transforming-events)
19
+ - [Best Practices](#best-practices)
20
+ - [Built-in Hooks](#built-in-hooks)
21
+ * [useMountEvent](#usemountevent)
22
+
23
+ <!-- tocstop -->
9
24
 
10
25
  ## Installation
11
26
 
@@ -18,7 +33,7 @@ yarn add react-event-tracking
18
33
 
19
34
  ## Quickstart
20
35
 
21
- 1. Define the root handler (e.g., send to GTM or API)
36
+ 1. Define the root handler (e.g., send to GTM, Amplitude or API)
22
37
  ```tsx
23
38
  import { TrackRoot } from 'react-event-tracking';
24
39
 
@@ -56,6 +71,140 @@ const MyButton = () => {
56
71
  };
57
72
  ```
58
73
 
74
+ ## Advanced Usage
75
+
76
+ ### Multiple Trackers & Factory
77
+
78
+ You can chain multiple `TrackRoot` components to send events to different analytics services. Events bubble up through all providers.
79
+
80
+ Use `TrackRoot.factory` to create reusable tracker components:
81
+
82
+ 1. Create specific trackers
83
+ ```tsx
84
+ const TrackRootGoogle = TrackRoot.factory(
85
+ (name, params) => gtag('event', name, params)
86
+ );
87
+
88
+ const TrackRootAmplitude = TrackRoot.factory(
89
+ (name, params) => amplitude.logEvent(name, params)
90
+ );
91
+ ```
92
+
93
+ 2. Compose them in your app
94
+ ```tsx
95
+ const App = () => (
96
+ <TrackRootGoogle>
97
+ <TrackRootAmplitude>
98
+ <MyApp />
99
+ </TrackRootAmplitude>
100
+ </TrackRootGoogle>
101
+ );
102
+ ```
103
+
104
+ ### Filtering Events
105
+
106
+ You can control which events are sent to which provider using the `filter` prop (or the second argument in `factory`). If the filter returns `false`, the event is skipped for that tracker but continues to bubble up to others.
107
+
108
+ ```tsx
109
+ // Google Analytics: only track page_* events
110
+ const TrackRootGoogle = TrackRoot.factory(
111
+ (name, params) => gtag('event', name, params),
112
+ (name) => name.startsWith('page_')
113
+ );
114
+
115
+ // Amplitude: track everything (filter is optional, defaults to allowing all events)
116
+ const TrackRootAmplitude = TrackRoot.factory(
117
+ (name, params) => ampltitude.logEvent(name, params),
118
+ );
119
+ ```
120
+
121
+ Compose them in your app:
122
+
123
+ ```tsx
124
+ const App = () => (
125
+ <TrackRootGoogle>
126
+ <TrackRootAmplitude>
127
+ <MyApp />
128
+ </TrackRootAmplitude>
129
+ </TrackRootGoogle>
130
+ );
131
+ ```
132
+
133
+ ### Transforming Events
134
+
135
+ You can modify the event name or parameters before they reach the handler using the `transform` prop (or the third argument in `factory`).
136
+
137
+ Note: Transformations apply locally and do not affect the event bubbling up to parent providers.
138
+
139
+ ```tsx
140
+ // GDPR Tracker
141
+ const AmpltitudeUS = TrackRoot.factory(
142
+ (name, params) => amplitude.logEvent(name, params),
143
+ undefined, // no filter
144
+ (name, params) => {
145
+ if (params?.userRegion === 'EU') {
146
+ // Remove PII (Personally Identifiable Information)
147
+ const { userId, email, ...safeParams } = params || {};
148
+ return {
149
+ eventName: name,
150
+ params: safeParams
151
+ };
152
+ }
153
+ return { eventName: name, params };
154
+ }
155
+ );
156
+ ```
157
+
158
+ ## Best Practices
159
+
160
+ A common pattern is to layer data from global to specific. Here is how parameters merge:
161
+
162
+ ```tsx
163
+ <TrackRoot onEvent={handleEvent}>
164
+ {/* 1. ROOT: Global data (App Version, Environment) */}
165
+ <TrackProvider params={{ appVersion: '1.0.0' }}>
166
+
167
+ {/* 2. PAGE: Screen-level context */}
168
+ <TrackProvider params={{ page: 'ProductDetails', category: 'Shoes' }}>
169
+
170
+ {/* 3. COMPONENT: Widget-level context */}
171
+ <TrackProvider params={{ productId: 'sku-999' }}>
172
+ <AddToCartButton />
173
+ </TrackProvider>
174
+
175
+ </TrackProvider>
176
+ </TrackProvider>
177
+ </TrackRoot>
178
+
179
+ // Inside AddToCartButton:
180
+ const { sendEvent } = useTracker();
181
+
182
+ // 4. EVENT: Action-specific data
183
+ // When clicked, we only pass what changed right now.
184
+ const handleClick = () => {
185
+ sendEvent('add_to_cart', { quantity: 1 });
186
+ };
187
+ ```
188
+
189
+ **Resulting Event Payload:**
190
+ The library merges all layers automatically. The handler receives:
191
+
192
+
193
+ ```js
194
+ {
195
+ // From Root
196
+ appVersion: '1.0.0',
197
+ // From Page Provider
198
+ page: 'ProductDetails',
199
+ category: 'Shoes',
200
+ // From Component Provider
201
+ productId: 'sku-999',
202
+ // From Event
203
+ quantity: 1
204
+ }
205
+ ```
206
+
207
+
59
208
  ## Built-in Hooks
60
209
 
61
210
  ### useMountEvent
package/dist/index.cjs CHANGED
@@ -14,25 +14,69 @@ const useTracker = () => {
14
14
  }
15
15
  return ctx;
16
16
  };
17
- const TrackRoot = ({
18
- onEvent,
19
- children
20
- }) => {
21
- const onEventRef = React.useRef(onEvent);
22
- onEventRef.current = onEvent;
23
- const sendEvent = React.useCallback((eventName, params) => {
24
- onEventRef.current(eventName, params);
25
- }, []);
17
+ const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
18
+ const parentCtx = React.useContext(TrackContext);
19
+ const onEventRef = useFreshRef(onEvent);
20
+ const filterRef = useFreshRef(filter);
21
+ const transformRef = useFreshRef(transform);
22
+ const sendEvent = React.useCallback(
23
+ (eventName, params) => {
24
+ let localName = eventName;
25
+ let localParams = params;
26
+ let shouldProcessLocal = true;
27
+ try {
28
+ if (filterRef.current) {
29
+ shouldProcessLocal = filterRef.current(localName, localParams);
30
+ }
31
+ } catch (error) {
32
+ console.error("TrackRoot filter failed:", error);
33
+ shouldProcessLocal = false;
34
+ }
35
+ if (shouldProcessLocal && transformRef.current) {
36
+ try {
37
+ const paramsCopy = params ? { ...params } : params;
38
+ const result = transformRef.current(eventName, paramsCopy);
39
+ localName = result.eventName;
40
+ localParams = result.params;
41
+ } catch (error) {
42
+ console.error("TrackRoot transform failed:", error);
43
+ shouldProcessLocal = false;
44
+ }
45
+ }
46
+ if (shouldProcessLocal) {
47
+ try {
48
+ onEventRef.current(localName, localParams);
49
+ } catch (error) {
50
+ console.error("TrackRoot onEvent failed:", error);
51
+ }
52
+ }
53
+ if (parentCtx) {
54
+ parentCtx.sendEvent(eventName, params);
55
+ }
56
+ },
57
+ [parentCtx]
58
+ );
26
59
  const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
27
60
  return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
28
61
  };
62
+ const factory = (onEvent, filter, transform) => {
63
+ return (props) => /* @__PURE__ */ React__default.createElement(
64
+ TrackRootComponent,
65
+ {
66
+ onEvent,
67
+ filter,
68
+ transform,
69
+ ...props
70
+ }
71
+ );
72
+ };
73
+ const TrackRoot = Object.assign(TrackRootComponent, { factory });
29
74
  const TrackProvider = ({
30
75
  params,
31
76
  children
32
77
  }) => {
33
78
  const ctx = useTracker();
34
- const paramsRef = React.useRef(params);
35
- paramsRef.current = params;
79
+ const paramsRef = useFreshRef(params);
36
80
  const sendEvent = React.useCallback(
37
81
  (eventName, eventParams) => {
38
82
  const currentParams = paramsRef.current;
@@ -46,6 +90,11 @@ const TrackProvider = ({
46
90
  const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
47
91
  return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
48
92
  };
93
+ function useFreshRef(data) {
94
+ const ref = React.useRef(data);
95
+ ref.current = data;
96
+ return ref;
97
+ }
49
98
 
50
99
  function useMountEvent(eventName, params) {
51
100
  const { sendEvent } = useTracker();
package/dist/index.d.cts CHANGED
@@ -5,11 +5,22 @@ type EventParams = Record<string, any>;
5
5
  type TrackContextValue = {
6
6
  sendEvent: (eventName: string, params?: EventParams) => void;
7
7
  };
8
+ type EventFilter = (eventName: string, params?: EventParams) => boolean;
9
+ type TransformedEvent = {
10
+ eventName: string;
11
+ params?: EventParams;
12
+ };
13
+ type EventTransformer = (eventName: string, params?: EventParams) => TransformedEvent;
8
14
 
9
15
  declare const useTracker: () => TrackContextValue;
10
- declare const TrackRoot: ({ onEvent, children }: PropsWithChildren<{
16
+ type TrackRootProps = PropsWithChildren<{
11
17
  onEvent: (eventName: string, params?: EventParams) => void;
12
- }>) => react_jsx_runtime.JSX.Element;
18
+ filter?: EventFilter;
19
+ transform?: EventTransformer;
20
+ }>;
21
+ declare const TrackRoot: (({ onEvent, children, filter, transform }: TrackRootProps) => react_jsx_runtime.JSX.Element) & {
22
+ factory: (onEvent: (eventName: string, params?: EventParams) => void, filter?: EventFilter, transform?: EventTransformer) => (props: Omit<TrackRootProps, "onEvent" | "filter" | "transform">) => react_jsx_runtime.JSX.Element;
23
+ };
13
24
  declare const TrackProvider: ({ params, children }: PropsWithChildren<{
14
25
  params: EventParams;
15
26
  }>) => react_jsx_runtime.JSX.Element;
package/dist/index.d.mts CHANGED
@@ -5,11 +5,22 @@ type EventParams = Record<string, any>;
5
5
  type TrackContextValue = {
6
6
  sendEvent: (eventName: string, params?: EventParams) => void;
7
7
  };
8
+ type EventFilter = (eventName: string, params?: EventParams) => boolean;
9
+ type TransformedEvent = {
10
+ eventName: string;
11
+ params?: EventParams;
12
+ };
13
+ type EventTransformer = (eventName: string, params?: EventParams) => TransformedEvent;
8
14
 
9
15
  declare const useTracker: () => TrackContextValue;
10
- declare const TrackRoot: ({ onEvent, children }: PropsWithChildren<{
16
+ type TrackRootProps = PropsWithChildren<{
11
17
  onEvent: (eventName: string, params?: EventParams) => void;
12
- }>) => react_jsx_runtime.JSX.Element;
18
+ filter?: EventFilter;
19
+ transform?: EventTransformer;
20
+ }>;
21
+ declare const TrackRoot: (({ onEvent, children, filter, transform }: TrackRootProps) => react_jsx_runtime.JSX.Element) & {
22
+ factory: (onEvent: (eventName: string, params?: EventParams) => void, filter?: EventFilter, transform?: EventTransformer) => (props: Omit<TrackRootProps, "onEvent" | "filter" | "transform">) => react_jsx_runtime.JSX.Element;
23
+ };
13
24
  declare const TrackProvider: ({ params, children }: PropsWithChildren<{
14
25
  params: EventParams;
15
26
  }>) => react_jsx_runtime.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -5,11 +5,22 @@ type EventParams = Record<string, any>;
5
5
  type TrackContextValue = {
6
6
  sendEvent: (eventName: string, params?: EventParams) => void;
7
7
  };
8
+ type EventFilter = (eventName: string, params?: EventParams) => boolean;
9
+ type TransformedEvent = {
10
+ eventName: string;
11
+ params?: EventParams;
12
+ };
13
+ type EventTransformer = (eventName: string, params?: EventParams) => TransformedEvent;
8
14
 
9
15
  declare const useTracker: () => TrackContextValue;
10
- declare const TrackRoot: ({ onEvent, children }: PropsWithChildren<{
16
+ type TrackRootProps = PropsWithChildren<{
11
17
  onEvent: (eventName: string, params?: EventParams) => void;
12
- }>) => react_jsx_runtime.JSX.Element;
18
+ filter?: EventFilter;
19
+ transform?: EventTransformer;
20
+ }>;
21
+ declare const TrackRoot: (({ onEvent, children, filter, transform }: TrackRootProps) => react_jsx_runtime.JSX.Element) & {
22
+ factory: (onEvent: (eventName: string, params?: EventParams) => void, filter?: EventFilter, transform?: EventTransformer) => (props: Omit<TrackRootProps, "onEvent" | "filter" | "transform">) => react_jsx_runtime.JSX.Element;
23
+ };
13
24
  declare const TrackProvider: ({ params, children }: PropsWithChildren<{
14
25
  params: EventParams;
15
26
  }>) => react_jsx_runtime.JSX.Element;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useContext, useRef, useCallback, useMemo, useEffect } from 'react';
1
+ import React, { useContext, useCallback, useMemo, useRef, useEffect } from 'react';
2
2
 
3
3
  const TrackContext = React.createContext(null);
4
4
  const useTracker = () => {
@@ -8,25 +8,69 @@ const useTracker = () => {
8
8
  }
9
9
  return ctx;
10
10
  };
11
- const TrackRoot = ({
12
- onEvent,
13
- children
14
- }) => {
15
- const onEventRef = useRef(onEvent);
16
- onEventRef.current = onEvent;
17
- const sendEvent = useCallback((eventName, params) => {
18
- onEventRef.current(eventName, params);
19
- }, []);
11
+ const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
12
+ const parentCtx = useContext(TrackContext);
13
+ const onEventRef = useFreshRef(onEvent);
14
+ const filterRef = useFreshRef(filter);
15
+ const transformRef = useFreshRef(transform);
16
+ const sendEvent = useCallback(
17
+ (eventName, params) => {
18
+ let localName = eventName;
19
+ let localParams = params;
20
+ let shouldProcessLocal = true;
21
+ try {
22
+ if (filterRef.current) {
23
+ shouldProcessLocal = filterRef.current(localName, localParams);
24
+ }
25
+ } catch (error) {
26
+ console.error("TrackRoot filter failed:", error);
27
+ shouldProcessLocal = false;
28
+ }
29
+ if (shouldProcessLocal && transformRef.current) {
30
+ try {
31
+ const paramsCopy = params ? { ...params } : params;
32
+ const result = transformRef.current(eventName, paramsCopy);
33
+ localName = result.eventName;
34
+ localParams = result.params;
35
+ } catch (error) {
36
+ console.error("TrackRoot transform failed:", error);
37
+ shouldProcessLocal = false;
38
+ }
39
+ }
40
+ if (shouldProcessLocal) {
41
+ try {
42
+ onEventRef.current(localName, localParams);
43
+ } catch (error) {
44
+ console.error("TrackRoot onEvent failed:", error);
45
+ }
46
+ }
47
+ if (parentCtx) {
48
+ parentCtx.sendEvent(eventName, params);
49
+ }
50
+ },
51
+ [parentCtx]
52
+ );
20
53
  const value = useMemo(() => ({ sendEvent }), [sendEvent]);
21
54
  return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
22
55
  };
56
+ const factory = (onEvent, filter, transform) => {
57
+ return (props) => /* @__PURE__ */ React.createElement(
58
+ TrackRootComponent,
59
+ {
60
+ onEvent,
61
+ filter,
62
+ transform,
63
+ ...props
64
+ }
65
+ );
66
+ };
67
+ const TrackRoot = Object.assign(TrackRootComponent, { factory });
23
68
  const TrackProvider = ({
24
69
  params,
25
70
  children
26
71
  }) => {
27
72
  const ctx = useTracker();
28
- const paramsRef = useRef(params);
29
- paramsRef.current = params;
73
+ const paramsRef = useFreshRef(params);
30
74
  const sendEvent = useCallback(
31
75
  (eventName, eventParams) => {
32
76
  const currentParams = paramsRef.current;
@@ -40,6 +84,11 @@ const TrackProvider = ({
40
84
  const value = useMemo(() => ({ sendEvent }), [sendEvent]);
41
85
  return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
42
86
  };
87
+ function useFreshRef(data) {
88
+ const ref = useRef(data);
89
+ ref.current = data;
90
+ return ref;
91
+ }
43
92
 
44
93
  function useMountEvent(eventName, params) {
45
94
  const { sendEvent } = useTracker();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-event-tracking",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "scripts": {
16
- "build": "tsc --noEmit && unbuild",
16
+ "build": "tsc --noEmit && unbuild && yarn toc",
17
17
  "test": "vitest",
18
18
  "toc": "npx markdown-toc README.md -i"
19
19
  },