react-event-tracking 1.0.4 → 1.0.6
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 +109 -9
- package/dist/index.cjs +62 -13
- package/dist/index.d.cts +14 -4
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.mjs +63 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# 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
|
-
|
|
66
|
-
|
|
163
|
+
<TrackRoot onEvent={handleEvent}>
|
|
164
|
+
{/* 1. ROOT: Global data (App Version, Environment) */}
|
|
165
|
+
<TrackProvider params={{ appVersion: '1.0.0' }}>
|
|
67
166
|
|
|
68
|
-
|
|
69
|
-
|
|
167
|
+
{/* 2. PAGE: Screen-level context */}
|
|
168
|
+
<TrackProvider params={{ page: 'ProductDetails', category: 'Shoes' }}>
|
|
70
169
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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,70 @@ const useTracker = () => {
|
|
|
14
14
|
}
|
|
15
15
|
return ctx;
|
|
16
16
|
};
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const EmptyParams = {};
|
|
18
|
+
const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
|
|
19
|
+
const parentCtx = React.useContext(TrackContext);
|
|
20
|
+
const onEventRef = useFreshRef(onEvent);
|
|
21
|
+
const filterRef = useFreshRef(filter);
|
|
22
|
+
const transformRef = useFreshRef(transform);
|
|
23
|
+
const sendEvent = React.useCallback(
|
|
24
|
+
(eventName, params) => {
|
|
25
|
+
let localName = eventName;
|
|
26
|
+
let localParams = params || EmptyParams;
|
|
27
|
+
let shouldProcessLocal = true;
|
|
28
|
+
try {
|
|
29
|
+
if (filterRef.current) {
|
|
30
|
+
shouldProcessLocal = filterRef.current(localName, localParams);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("TrackRoot filter failed:", error);
|
|
34
|
+
shouldProcessLocal = false;
|
|
35
|
+
}
|
|
36
|
+
if (shouldProcessLocal && transformRef.current) {
|
|
37
|
+
try {
|
|
38
|
+
const paramsCopy = params ? { ...params } : EmptyParams;
|
|
39
|
+
const result = transformRef.current(eventName, paramsCopy);
|
|
40
|
+
localName = result.eventName;
|
|
41
|
+
localParams = result.params;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("TrackRoot transform failed:", error);
|
|
44
|
+
shouldProcessLocal = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (shouldProcessLocal) {
|
|
48
|
+
try {
|
|
49
|
+
onEventRef.current(localName, localParams);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error("TrackRoot onEvent failed:", error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (parentCtx) {
|
|
55
|
+
parentCtx.sendEvent(eventName, params);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[parentCtx]
|
|
59
|
+
);
|
|
27
60
|
const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
28
|
-
return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value },
|
|
61
|
+
return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
|
|
29
62
|
};
|
|
63
|
+
const factory = (onEvent, filter, transform) => {
|
|
64
|
+
return (props) => /* @__PURE__ */ React__default.createElement(
|
|
65
|
+
TrackRootComponent,
|
|
66
|
+
{
|
|
67
|
+
onEvent,
|
|
68
|
+
filter,
|
|
69
|
+
transform,
|
|
70
|
+
...props
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
const TrackRoot = Object.assign(TrackRootComponent, { factory });
|
|
30
75
|
const TrackProvider = ({
|
|
31
76
|
params,
|
|
32
77
|
children
|
|
33
78
|
}) => {
|
|
34
79
|
const ctx = useTracker();
|
|
35
|
-
const paramsRef =
|
|
36
|
-
paramsRef.current = params;
|
|
80
|
+
const paramsRef = useFreshRef(params);
|
|
37
81
|
const sendEvent = React.useCallback(
|
|
38
82
|
(eventName, eventParams) => {
|
|
39
83
|
const currentParams = paramsRef.current;
|
|
@@ -47,6 +91,11 @@ const TrackProvider = ({
|
|
|
47
91
|
const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
48
92
|
return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
|
|
49
93
|
};
|
|
94
|
+
function useFreshRef(data) {
|
|
95
|
+
const ref = React.useRef(data);
|
|
96
|
+
ref.current = data;
|
|
97
|
+
return ref;
|
|
98
|
+
}
|
|
50
99
|
|
|
51
100
|
function useMountEvent(eventName, params) {
|
|
52
101
|
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
|
-
|
|
11
|
-
onEvent: (eventName: string, params
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
type TrackRootProps = PropsWithChildren<{
|
|
17
|
+
onEvent: (eventName: string, params: EventParams) => void;
|
|
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
|
-
|
|
11
|
-
onEvent: (eventName: string, params
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
type TrackRootProps = PropsWithChildren<{
|
|
17
|
+
onEvent: (eventName: string, params: EventParams) => void;
|
|
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
|
-
|
|
11
|
-
onEvent: (eventName: string, params
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
type TrackRootProps = PropsWithChildren<{
|
|
17
|
+
onEvent: (eventName: string, params: EventParams) => void;
|
|
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,
|
|
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,70 @@ const useTracker = () => {
|
|
|
8
8
|
}
|
|
9
9
|
return ctx;
|
|
10
10
|
};
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const EmptyParams = {};
|
|
12
|
+
const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
|
|
13
|
+
const parentCtx = useContext(TrackContext);
|
|
14
|
+
const onEventRef = useFreshRef(onEvent);
|
|
15
|
+
const filterRef = useFreshRef(filter);
|
|
16
|
+
const transformRef = useFreshRef(transform);
|
|
17
|
+
const sendEvent = useCallback(
|
|
18
|
+
(eventName, params) => {
|
|
19
|
+
let localName = eventName;
|
|
20
|
+
let localParams = params || EmptyParams;
|
|
21
|
+
let shouldProcessLocal = true;
|
|
22
|
+
try {
|
|
23
|
+
if (filterRef.current) {
|
|
24
|
+
shouldProcessLocal = filterRef.current(localName, localParams);
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("TrackRoot filter failed:", error);
|
|
28
|
+
shouldProcessLocal = false;
|
|
29
|
+
}
|
|
30
|
+
if (shouldProcessLocal && transformRef.current) {
|
|
31
|
+
try {
|
|
32
|
+
const paramsCopy = params ? { ...params } : EmptyParams;
|
|
33
|
+
const result = transformRef.current(eventName, paramsCopy);
|
|
34
|
+
localName = result.eventName;
|
|
35
|
+
localParams = result.params;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error("TrackRoot transform failed:", error);
|
|
38
|
+
shouldProcessLocal = false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (shouldProcessLocal) {
|
|
42
|
+
try {
|
|
43
|
+
onEventRef.current(localName, localParams);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("TrackRoot onEvent failed:", error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (parentCtx) {
|
|
49
|
+
parentCtx.sendEvent(eventName, params);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
[parentCtx]
|
|
53
|
+
);
|
|
21
54
|
const value = useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
22
|
-
return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value },
|
|
55
|
+
return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
|
|
23
56
|
};
|
|
57
|
+
const factory = (onEvent, filter, transform) => {
|
|
58
|
+
return (props) => /* @__PURE__ */ React.createElement(
|
|
59
|
+
TrackRootComponent,
|
|
60
|
+
{
|
|
61
|
+
onEvent,
|
|
62
|
+
filter,
|
|
63
|
+
transform,
|
|
64
|
+
...props
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
const TrackRoot = Object.assign(TrackRootComponent, { factory });
|
|
24
69
|
const TrackProvider = ({
|
|
25
70
|
params,
|
|
26
71
|
children
|
|
27
72
|
}) => {
|
|
28
73
|
const ctx = useTracker();
|
|
29
|
-
const paramsRef =
|
|
30
|
-
paramsRef.current = params;
|
|
74
|
+
const paramsRef = useFreshRef(params);
|
|
31
75
|
const sendEvent = useCallback(
|
|
32
76
|
(eventName, eventParams) => {
|
|
33
77
|
const currentParams = paramsRef.current;
|
|
@@ -41,6 +85,11 @@ const TrackProvider = ({
|
|
|
41
85
|
const value = useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
42
86
|
return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
|
|
43
87
|
};
|
|
88
|
+
function useFreshRef(data) {
|
|
89
|
+
const ref = useRef(data);
|
|
90
|
+
ref.current = data;
|
|
91
|
+
return ref;
|
|
92
|
+
}
|
|
44
93
|
|
|
45
94
|
function useMountEvent(eventName, params) {
|
|
46
95
|
const { sendEvent } = useTracker();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-event-tracking",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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
|
},
|