react-event-tracking 1.0.4 → 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
@@ -1,4 +1,3 @@
1
-
2
1
  # react-event-tracking [![NPM Version](https://img.shields.io/npm/v/react-event-tracking)](https://www.npmjs.com/package/react-event-tracking)
3
2
  A convenient React context for tracking analytics events.
4
3
 
@@ -7,6 +6,21 @@ A convenient React context for tracking analytics events.
7
6
  - **Nested Contexts**: Automatically merges parameters from parent providers.
8
7
  - **Zero Re-renders**: No need to wrap props in `useCallback`/`useMemo`.
9
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 -->
10
24
 
11
25
  ## Installation
12
26
 
@@ -57,22 +71,108 @@ const MyButton = () => {
57
71
  };
58
72
  ```
59
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
+
60
158
  ## Best Practices
61
159
 
62
160
  A common pattern is to layer data from global to specific. Here is how parameters merge:
63
161
 
64
162
  ```tsx
65
- // 1. ROOT: Global data (App Version, Environment)
66
- <TrackRoot onEvent={handleEvent} initialParams={{ appVersion: '1.0.0' }}>
163
+ <TrackRoot onEvent={handleEvent}>
164
+ {/* 1. ROOT: Global data (App Version, Environment) */}
165
+ <TrackProvider params={{ appVersion: '1.0.0' }}>
67
166
 
68
- {/* 2. PAGE: Screen-level context */}
69
- <TrackProvider params={{ page: 'ProductDetails', category: 'Shoes' }}>
167
+ {/* 2. PAGE: Screen-level context */}
168
+ <TrackProvider params={{ page: 'ProductDetails', category: 'Shoes' }}>
70
169
 
71
- {/* 3. COMPONENT: Widget-level context */}
72
- <TrackProvider params={{ productId: 'sku-999' }}>
73
- <AddToCartButton />
74
- </TrackProvider>
170
+ {/* 3. COMPONENT: Widget-level context */}
171
+ <TrackProvider params={{ productId: 'sku-999' }}>
172
+ <AddToCartButton />
173
+ </TrackProvider>
75
174
 
175
+ </TrackProvider>
76
176
  </TrackProvider>
77
177
  </TrackRoot>
78
178
 
package/dist/index.cjs CHANGED
@@ -14,26 +14,69 @@ const useTracker = () => {
14
14
  }
15
15
  return ctx;
16
16
  };
17
- const TrackRoot = ({
18
- onEvent,
19
- children,
20
- initialParams
21
- }) => {
22
- const onEventRef = React.useRef(onEvent);
23
- onEventRef.current = onEvent;
24
- const sendEvent = React.useCallback((eventName, params) => {
25
- onEventRef.current(eventName, params);
26
- }, []);
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
+ );
27
59
  const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
28
- return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, /* @__PURE__ */ React__default.createElement(TrackProvider, { params: initialParams || {} }, children));
60
+ return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
29
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 });
30
74
  const TrackProvider = ({
31
75
  params,
32
76
  children
33
77
  }) => {
34
78
  const ctx = useTracker();
35
- const paramsRef = React.useRef(params);
36
- paramsRef.current = params;
79
+ const paramsRef = useFreshRef(params);
37
80
  const sendEvent = React.useCallback(
38
81
  (eventName, eventParams) => {
39
82
  const currentParams = paramsRef.current;
@@ -47,6 +90,11 @@ const TrackProvider = ({
47
90
  const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
48
91
  return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
49
92
  };
93
+ function useFreshRef(data) {
94
+ const ref = React.useRef(data);
95
+ ref.current = data;
96
+ return ref;
97
+ }
50
98
 
51
99
  function useMountEvent(eventName, params) {
52
100
  const { sendEvent } = useTracker();
package/dist/index.d.cts CHANGED
@@ -5,12 +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, initialParams }: PropsWithChildren<{
16
+ type TrackRootProps = PropsWithChildren<{
11
17
  onEvent: (eventName: string, params?: EventParams) => void;
12
- initialParams?: EventParams;
13
- }>) => 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
+ };
14
24
  declare const TrackProvider: ({ params, children }: PropsWithChildren<{
15
25
  params: EventParams;
16
26
  }>) => react_jsx_runtime.JSX.Element;
package/dist/index.d.mts CHANGED
@@ -5,12 +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, initialParams }: PropsWithChildren<{
16
+ type TrackRootProps = PropsWithChildren<{
11
17
  onEvent: (eventName: string, params?: EventParams) => void;
12
- initialParams?: EventParams;
13
- }>) => 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
+ };
14
24
  declare const TrackProvider: ({ params, children }: PropsWithChildren<{
15
25
  params: EventParams;
16
26
  }>) => react_jsx_runtime.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -5,12 +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, initialParams }: PropsWithChildren<{
16
+ type TrackRootProps = PropsWithChildren<{
11
17
  onEvent: (eventName: string, params?: EventParams) => void;
12
- initialParams?: EventParams;
13
- }>) => 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
+ };
14
24
  declare const TrackProvider: ({ params, children }: PropsWithChildren<{
15
25
  params: EventParams;
16
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,26 +8,69 @@ const useTracker = () => {
8
8
  }
9
9
  return ctx;
10
10
  };
11
- const TrackRoot = ({
12
- onEvent,
13
- children,
14
- initialParams
15
- }) => {
16
- const onEventRef = useRef(onEvent);
17
- onEventRef.current = onEvent;
18
- const sendEvent = useCallback((eventName, params) => {
19
- onEventRef.current(eventName, params);
20
- }, []);
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
+ );
21
53
  const value = useMemo(() => ({ sendEvent }), [sendEvent]);
22
- return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, /* @__PURE__ */ React.createElement(TrackProvider, { params: initialParams || {} }, children));
54
+ return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
23
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 });
24
68
  const TrackProvider = ({
25
69
  params,
26
70
  children
27
71
  }) => {
28
72
  const ctx = useTracker();
29
- const paramsRef = useRef(params);
30
- paramsRef.current = params;
73
+ const paramsRef = useFreshRef(params);
31
74
  const sendEvent = useCallback(
32
75
  (eventName, eventParams) => {
33
76
  const currentParams = paramsRef.current;
@@ -41,6 +84,11 @@ const TrackProvider = ({
41
84
  const value = useMemo(() => ({ sendEvent }), [sendEvent]);
42
85
  return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
43
86
  };
87
+ function useFreshRef(data) {
88
+ const ref = useRef(data);
89
+ ref.current = data;
90
+ return ref;
91
+ }
44
92
 
45
93
  function useMountEvent(eventName, params) {
46
94
  const { sendEvent } = useTracker();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-event-tracking",
3
- "version": "1.0.4",
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
  },