react-event-tracking 1.0.9 → 2.0.0

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
@@ -3,6 +3,7 @@ A convenient React context for tracking analytics events.
3
3
 
4
4
  ## Features
5
5
 
6
+ - **Elegant Type-Safe Api**: enjoy a seamless dot-notation experience with full TypeScript autocompletion.
6
7
  - **Nested Contexts**: Automatically merges parameters from parent providers.
7
8
  - **Zero Re-renders**: No need to wrap props in `useCallback`/`useMemo`.
8
9
  - **Multiple Providers**: Send events to different analytics services.
@@ -15,6 +16,10 @@ A convenient React context for tracking analytics events.
15
16
 
16
17
  - [Installation](#installation)
17
18
  - [Quickstart](#quickstart)
19
+ - [Usage Guide](#usage-guide)
20
+ * [Basic Hook](#basic-hook)
21
+ * [Typed Hook Factory](#typed-hook-factory)
22
+ * [Custom Handlers](#custom-handlers)
18
23
  - [Advanced Usage](#advanced-usage)
19
24
  * [Multiple Trackers & Factory](#multiple-trackers--factory)
20
25
  * [Filtering Events](#filtering-events)
@@ -47,6 +52,7 @@ const Main = () => (
47
52
  </TrackRoot>
48
53
  );
49
54
  ```
55
+
50
56
  2. Wrap any component with shared parameters
51
57
  ```tsx
52
58
  import { TrackProvider } from 'react-event-tracking';
@@ -58,29 +64,129 @@ const Dashboard = () => (
58
64
  );
59
65
  ```
60
66
 
61
- 3. Send events conveniently. On button click, parameters will be merged.
67
+ ## Usage Guide
68
+
69
+ ### Basic Hook
70
+
71
+ Use `useReactEventTracking` for simple event tracking
72
+
62
73
  ```tsx
63
- import { useTracker } from 'react-event-tracking';
74
+ import { useReactEventTracking } from 'react-event-tracking';
64
75
 
65
76
  const MyButton = () => {
66
- const { sendEvent } = useTracker();
77
+ const { track } = useReactEventTracking();
67
78
 
68
79
  return (
69
- <>
70
- // event sent with parameters: { screen: 'dashboard', button_id: '123' }
71
- <button onClick={() => sendEvent('click', { button_id: '123' })}>
80
+ {/* Option A: String } */}
81
+ <button onClick={() => track('click', { button_id: '123' })}>
82
+ Click me
83
+ </button>
84
+
85
+ {/* Option B: Object call */}
86
+ <button onClick={() => track({ eventName: 'click', params: { button_id: '456' } })}>
72
87
  Click me
73
88
  </button>
74
89
 
75
- {/* Option B: Object call */}
76
- <button onClick={() => sendEvent({ eventName: 'click', params: { button_id: '456' } })}>
77
- Click me too
78
- </button>
79
- </>
80
90
  );
81
91
  };
82
92
  ```
83
93
 
94
+ ### Typed Hook Factory
95
+
96
+ For a more convenient dot-notation syntax and full TypeScript support, create your own hook using `createReactEventTrackingHook`.
97
+
98
+ 1. Create a hook:
99
+ ```tsx
100
+ import { createReactEventTrackingHook } from 'react-event-tracking';
101
+
102
+ // LoadingScreen.tsx
103
+ export type LoginScreenEvents = {
104
+ forgot_password: { from: "footer" | "button" }
105
+ logged_in: { timePassed: number }
106
+ }
107
+
108
+ type SystemEvents = {
109
+ app_updated: { previous_version: string; current_version: string }
110
+ }
111
+
112
+ // analytics.ts
113
+ export type AnalyticsEvents = SystemEvents & {
114
+ login_screen: LoginScreenEvents
115
+ }
116
+
117
+ export const useTracking = createReactEventTrackingHook<AnalyticsEvents>();
118
+ ```
119
+
120
+ 2. Use it in your components:
121
+ ```tsx
122
+ const LoginButton = () => {
123
+ const { track } = useTracking();
124
+
125
+ const handleLogin = () => {
126
+ // This call transforms into "login_screen.logged_in" event with parameters
127
+ track.login_screen.logged_in({ timePassed: 3000 });
128
+ };
129
+ return (
130
+ <button onClick={handleLogin}>
131
+ Login with Google
132
+ </button>
133
+ );
134
+ };
135
+ ```
136
+
137
+ ### Custom Handlers
138
+
139
+ Sometimes you need to expose more than just the `track` function, for example, a way to identify users. You can pass custom handlers to the factory.
140
+
141
+ 1. Define your handlers type and create the hook:
142
+ ```tsx
143
+ type MyCustomHandlers = {
144
+ setUserId: (id: string) => void;
145
+ }
146
+
147
+ export const useTracking = createReactEventTrackingHook<AnalyticsEvents, MyCustomHandlers>();
148
+ ```
149
+
150
+ 2. Create a custom Root using [TrackRoot.factory](#multiple-trackers--factory):
151
+ ```tsx
152
+ import { TrackRoot } from 'react-event-tracking';
153
+
154
+ const CustomTrackRoot = TrackRoot.factory<MyCustomHandlers>({
155
+ onEvent: (name, params) => {
156
+ amplitude.logEvent(name, params);
157
+ },
158
+ customHandlers: {
159
+ setUserId: (id) => {
160
+ amplitude.setUserId(id);
161
+ }
162
+ }
163
+ });
164
+
165
+ const App = () => (
166
+ <CustomTrackRoot>
167
+ <Main />
168
+ </CustomTrackRoot>
169
+ );
170
+ ```
171
+
172
+ 3. Use it in your components:
173
+ ```tsx
174
+ const Profile = () => {
175
+ const { track, setUserId } = useTracking();
176
+
177
+ const handleLogin = () => {
178
+ track.login_screen.logged_in({ timePassed: 3000 });
179
+ setUserId('user_123')
180
+ };
181
+
182
+ return (
183
+ <button onClick={handleLogin}>
184
+ Login
185
+ </button>
186
+ );
187
+ }
188
+ ```
189
+
84
190
  ## Advanced Usage
85
191
 
86
192
  ### Multiple Trackers & Factory
@@ -91,13 +197,13 @@ Use `TrackRoot.factory` to create reusable tracker components:
91
197
 
92
198
  1. Create specific trackers
93
199
  ```tsx
94
- const TrackRootGoogle = TrackRoot.factory(
95
- (name, params) => gtag('event', name, params)
96
- );
200
+ const TrackRootGoogle = TrackRoot.factory({
201
+ onEvent: (name, params) => gtag('event', name, params)
202
+ });
97
203
 
98
- const TrackRootAmplitude = TrackRoot.factory(
99
- (name, params) => amplitude.logEvent(name, params)
100
- );
204
+ const TrackRootAmplitude = TrackRoot.factory({
205
+ onEvent: (name, params) => amplitude.logEvent(name, params)
206
+ });
101
207
  ```
102
208
 
103
209
  2. Compose them in your app
@@ -113,19 +219,19 @@ const App = () => (
113
219
 
114
220
  ### Filtering Events
115
221
 
116
- 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.
222
+ You can control which events are sent to which provider using the `filter` prop in `factory`. If the filter returns `false`, the event is skipped for that tracker but continues to bubble up to others.
117
223
 
118
224
  ```tsx
119
225
  // Google Analytics: only track page_* events
120
- const TrackRootGoogle = TrackRoot.factory(
121
- (name, params) => gtag('event', name, params),
122
- (name) => name.startsWith('page_')
123
- );
124
-
125
- // Amplitude: track everything (filter is optional, defaults to allowing all events)
126
- const TrackRootAmplitude = TrackRoot.factory(
127
- (name, params) => ampltitude.logEvent(name, params),
128
- );
226
+ const TrackRootGoogle = TrackRoot.factory({
227
+ onEvent: (name, params) => gtag('event', name, params),
228
+ filter: (name) => name.startsWith('page_')
229
+ });
230
+
231
+ // Amplitude: track everything
232
+ const TrackRootAmplitude = TrackRoot.factory({
233
+ onEvent: (name, params) => amplitude.logEvent(name, params),
234
+ });
129
235
  ```
130
236
 
131
237
  Compose them in your app:
@@ -142,16 +248,15 @@ const App = () => (
142
248
 
143
249
  ### Transforming Events
144
250
 
145
- You can modify the event name or parameters before they reach the handler using the `transform` prop (or the third argument in `factory`).
251
+ You can modify the event name or parameters before they are sent to the handler using the `transform` prop in `factory`.
146
252
 
147
253
  Note: Transformations apply locally and do not affect the event bubbling up to parent providers.
148
254
 
149
255
  ```tsx
150
256
  // GDPR Tracker
151
- const AmpltitudeUS = TrackRoot.factory(
152
- (name, params) => amplitude.logEvent(name, params),
153
- undefined, // no filter
154
- (name, params) => {
257
+ const AmplitudeUS = TrackRoot.factory({
258
+ onEvent: (name, params) => amplitude.logEvent(name, params),
259
+ transform: (name, params) => {
155
260
  if (params?.userRegion === 'EU') {
156
261
  // Remove PII (Personally Identifiable Information)
157
262
  const { userId, email, ...safeParams } = params;
@@ -162,7 +267,7 @@ const AmpltitudeUS = TrackRoot.factory(
162
267
  }
163
268
  return { eventName: name, params };
164
269
  }
165
- );
270
+ });
166
271
  ```
167
272
 
168
273
  ### TypeScript Generics Support
@@ -170,13 +275,12 @@ const AmpltitudeUS = TrackRoot.factory(
170
275
  `TrackProvider` supports generics for strict typing of the passed parameters.
171
276
 
172
277
  ```tsx
173
- interface PageParams {
174
- screenId: string;
175
- isAuthorized: boolean;
278
+ interface ScreenParams {
279
+ screen: "dashboard" | "authScreen"
176
280
  }
177
281
 
178
282
  const MyPage = () => (
179
- <TrackProvider<PageParams> params={{ screenId: 'main', isAuthorized: true }}>
283
+ <TrackProvider<ScreenParams> params={{ screen: 'dashboard' }}>
180
284
  <Content />
181
285
  </TrackProvider>
182
286
  );
@@ -204,12 +308,12 @@ A common pattern is to layer data from global to specific. Here is how parameter
204
308
  </TrackRoot>
205
309
 
206
310
  // Inside AddToCartButton:
207
- const { sendEvent } = useTracker();
311
+ const { track } = useReactEventTracking();
208
312
 
209
313
  // 4. EVENT: Action-specific data
210
314
  // When clicked, we only pass what changed right now.
211
315
  const handleClick = () => {
212
- sendEvent('add_to_cart', { quantity: 1 });
316
+ track('add_to_cart', { quantity: 1 });
213
317
  };
214
318
  ```
215
319
 
package/dist/index.cjs CHANGED
@@ -20,20 +20,27 @@ function parseEventArgs(eventNameOrObject, eventParams) {
20
20
  }
21
21
 
22
22
  const TrackContext = React__default.createContext(null);
23
- const useTracker = () => {
23
+ const useReactEventTracking = () => {
24
24
  const ctx = React.useContext(TrackContext);
25
25
  if (!ctx) {
26
- throw new Error("useTracker must be used within TrackRoot");
26
+ throw new Error("useReactEventTracking must be used within TrackRoot");
27
27
  }
28
28
  return ctx;
29
29
  };
30
30
  const EmptyParams = {};
31
- const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
31
+ const TrackRootComponent = ({
32
+ onEvent,
33
+ children,
34
+ filter,
35
+ transform,
36
+ customHandlers
37
+ }) => {
32
38
  const parentCtx = React.useContext(TrackContext);
33
39
  const onEventRef = useFreshRef(onEvent);
34
40
  const filterRef = useFreshRef(filter);
35
41
  const transformRef = useFreshRef(transform);
36
- function sendEvent(eventNameOrObject, eventParams) {
42
+ const customHandlersRef = useFreshRef(customHandlers);
43
+ function track(eventNameOrObject, eventParams) {
37
44
  const { eventName, params: incomingParams } = parseEventArgs(
38
45
  eventNameOrObject,
39
46
  eventParams
@@ -68,20 +75,44 @@ const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
68
75
  }
69
76
  }
70
77
  if (parentCtx) {
71
- parentCtx.sendEvent(eventName, incomingParams);
78
+ parentCtx.track(eventName, incomingParams);
72
79
  }
73
80
  }
74
- const sendEventCached = React.useCallback(sendEvent, [parentCtx]);
75
- const value = React.useMemo(() => ({ sendEvent: sendEventCached }), [sendEventCached]);
81
+ const value = React.useMemo(() => {
82
+ return new Proxy({}, {
83
+ get(_, prop) {
84
+ if (prop === track.name) return track;
85
+ const handler = customHandlersRef.current?.[prop];
86
+ if (typeof handler === "function") {
87
+ return (...args) => handler(...args);
88
+ }
89
+ return void 0;
90
+ },
91
+ has(_, prop) {
92
+ return prop === track.name || (customHandlersRef.current ? prop in customHandlersRef.current : false);
93
+ },
94
+ ownKeys() {
95
+ const customKeys = customHandlersRef.current ? Object.keys(customHandlersRef.current) : [];
96
+ return Array.from(/* @__PURE__ */ new Set([track.name, ...customKeys]));
97
+ },
98
+ getOwnPropertyDescriptor(_, prop) {
99
+ return {
100
+ enumerable: true,
101
+ configurable: true
102
+ };
103
+ }
104
+ });
105
+ }, [parentCtx]);
76
106
  return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
77
107
  };
78
- const factory = (onEvent, filter, transform) => {
108
+ const factory = (args) => {
79
109
  return (props) => /* @__PURE__ */ React__default.createElement(
80
110
  TrackRootComponent,
81
111
  {
82
- onEvent,
83
- filter,
84
- transform,
112
+ onEvent: args.onEvent,
113
+ filter: args.filter,
114
+ transform: args.transform,
115
+ customHandlers: args.customHandlers,
85
116
  ...props
86
117
  }
87
118
  );
@@ -91,21 +122,20 @@ const TrackProvider = ({
91
122
  params,
92
123
  children
93
124
  }) => {
94
- const ctx = useTracker();
125
+ const ctx = useReactEventTracking();
95
126
  const paramsRef = useFreshRef(params);
96
- function sendEvent(eventNameOrObject, eventParams) {
127
+ function track(eventNameOrObject, eventParams) {
97
128
  const { eventName, params: incomingParams } = parseEventArgs(
98
129
  eventNameOrObject,
99
130
  eventParams
100
131
  );
101
132
  const currentParams = paramsRef.current;
102
- ctx.sendEvent(eventName, {
133
+ ctx.track(eventName, {
103
134
  ...currentParams,
104
135
  ...incomingParams
105
136
  });
106
137
  }
107
- const sendEventCached = React.useCallback(sendEvent, [ctx]);
108
- const value = React.useMemo(() => ({ sendEvent: sendEventCached }), [sendEventCached]);
138
+ const value = React.useMemo(() => ({ ...ctx, track }), [ctx]);
109
139
  return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
110
140
  };
111
141
  function useFreshRef(data) {
@@ -115,7 +145,7 @@ function useFreshRef(data) {
115
145
  }
116
146
 
117
147
  function useMountEvent(eventNameOrObject, eventParams) {
118
- const { sendEvent } = useTracker();
148
+ const { track } = useReactEventTracking();
119
149
  const counterRef = React.useRef(0);
120
150
  const { eventName, params } = parseEventArgs(eventNameOrObject, eventParams);
121
151
  React.useEffect(() => {
@@ -123,11 +153,40 @@ function useMountEvent(eventNameOrObject, eventParams) {
123
153
  return;
124
154
  }
125
155
  counterRef.current++;
126
- sendEvent(eventName, params);
156
+ track(eventName, params);
127
157
  }, []);
128
158
  }
129
159
 
160
+ function createTracker(path = [], track) {
161
+ return new Proxy(() => {
162
+ }, {
163
+ get(_, prop) {
164
+ return createTracker([...path, prop], track);
165
+ },
166
+ apply(_, __, [params]) {
167
+ const eventName = path.join(".");
168
+ track(eventName, params);
169
+ }
170
+ });
171
+ }
172
+
173
+ function createReactEventTrackingHook() {
174
+ function useTracking(scope) {
175
+ const ctx = useReactEventTracking();
176
+ const tracker = React.useMemo(() => {
177
+ const prefix = scope ? [String(scope)] : [];
178
+ return {
179
+ ...ctx,
180
+ track: createTracker(prefix, ctx.track)
181
+ };
182
+ }, [ctx, scope]);
183
+ return tracker;
184
+ }
185
+ return useTracking;
186
+ }
187
+
130
188
  exports.TrackProvider = TrackProvider;
131
189
  exports.TrackRoot = TrackRoot;
190
+ exports.createReactEventTrackingHook = createReactEventTrackingHook;
132
191
  exports.useMountEvent = useMountEvent;
133
- exports.useTracker = useTracker;
192
+ exports.useReactEventTracking = useReactEventTracking;
package/dist/index.d.cts CHANGED
@@ -2,13 +2,19 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PropsWithChildren } from 'react';
3
3
 
4
4
  type EventParams<T extends Record<string, any> = Record<string, any>> = T;
5
- interface TrackContextValue {
6
- sendEvent(eventName: string, params?: EventParams): void;
7
- sendEvent(event: EventObject): void;
5
+ type EventsMap = Record<string, any>;
6
+ type AnyFunction = (...args: any[]) => any;
7
+ type IsLeaf<T> = T extends Record<string, any> ? (keyof T extends never ? true : T[keyof T] extends Record<string, any> ? false : true) : true;
8
+ type FlatTracker<T> = {
9
+ [K in keyof T]: IsLeaf<T[K]> extends true ? (params: T[K]) => void : FlatTracker<T[K]>;
10
+ };
11
+ interface TrackContextValueLegacy {
12
+ track(eventName: string, params?: EventParams): void;
13
+ track(event: EventObject): void;
8
14
  }
9
- type EventObject = {
10
- eventName: string;
11
- params?: EventParams;
15
+ type EventObject<E extends string = string, P = EventParams> = {
16
+ eventName: E;
17
+ params?: P;
12
18
  };
13
19
  type EventFilter = (eventName: string, params: EventParams) => boolean;
14
20
  type TransformedEvent = {
@@ -17,14 +23,15 @@ type TransformedEvent = {
17
23
  };
18
24
  type EventTransformer = (eventName: string, params: EventParams) => TransformedEvent;
19
25
 
20
- declare const useTracker: () => TrackContextValue;
21
- type TrackRootProps = PropsWithChildren<{
26
+ declare const useReactEventTracking: () => TrackContextValueLegacy;
27
+ type TrackRootProps<CustomHandlers extends Record<string, AnyFunction>> = PropsWithChildren<{
22
28
  onEvent: (eventName: string, params: EventParams) => void;
23
29
  filter?: EventFilter;
24
30
  transform?: EventTransformer;
31
+ customHandlers?: CustomHandlers;
25
32
  }>;
26
- declare const TrackRoot: (({ onEvent, children, filter, transform }: TrackRootProps) => react_jsx_runtime.JSX.Element) & {
27
- factory: (onEvent: (eventName: string, params?: EventParams) => void, filter?: EventFilter, transform?: EventTransformer) => (props: Omit<TrackRootProps, "onEvent" | "filter" | "transform">) => react_jsx_runtime.JSX.Element;
33
+ declare const TrackRoot: (<CustomHandlers extends Record<string, AnyFunction> = {}>({ onEvent, children, filter, transform, customHandlers }: TrackRootProps<CustomHandlers>) => react_jsx_runtime.JSX.Element) & {
34
+ factory: <T extends Record<string, AnyFunction> = {}>(args: TrackRootProps<T>) => (props: Omit<TrackRootProps<T>, "onEvent" | "filter" | "transform" | "customHandlers">) => react_jsx_runtime.JSX.Element;
28
35
  };
29
36
  declare const TrackProvider: <T extends Record<string, any>>({ params, children }: PropsWithChildren<{
30
37
  params: EventParams<T>;
@@ -33,4 +40,13 @@ declare const TrackProvider: <T extends Record<string, any>>({ params, children
33
40
  declare function useMountEvent(eventName: string, params?: EventParams): void;
34
41
  declare function useMountEvent(event: EventObject): void;
35
42
 
36
- export { TrackProvider, TrackRoot, useMountEvent, useTracker };
43
+ declare function createReactEventTrackingHook<Map extends EventsMap, CustomHandlers extends Record<string, AnyFunction> = {}>(): {
44
+ (): {
45
+ track: FlatTracker<Map>;
46
+ } & CustomHandlers;
47
+ <K extends keyof Map>(scope: K): {
48
+ track: FlatTracker<Map>[K];
49
+ } & CustomHandlers;
50
+ };
51
+
52
+ export { TrackProvider, TrackRoot, createReactEventTrackingHook, useMountEvent, useReactEventTracking };
package/dist/index.d.mts CHANGED
@@ -2,13 +2,19 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PropsWithChildren } from 'react';
3
3
 
4
4
  type EventParams<T extends Record<string, any> = Record<string, any>> = T;
5
- interface TrackContextValue {
6
- sendEvent(eventName: string, params?: EventParams): void;
7
- sendEvent(event: EventObject): void;
5
+ type EventsMap = Record<string, any>;
6
+ type AnyFunction = (...args: any[]) => any;
7
+ type IsLeaf<T> = T extends Record<string, any> ? (keyof T extends never ? true : T[keyof T] extends Record<string, any> ? false : true) : true;
8
+ type FlatTracker<T> = {
9
+ [K in keyof T]: IsLeaf<T[K]> extends true ? (params: T[K]) => void : FlatTracker<T[K]>;
10
+ };
11
+ interface TrackContextValueLegacy {
12
+ track(eventName: string, params?: EventParams): void;
13
+ track(event: EventObject): void;
8
14
  }
9
- type EventObject = {
10
- eventName: string;
11
- params?: EventParams;
15
+ type EventObject<E extends string = string, P = EventParams> = {
16
+ eventName: E;
17
+ params?: P;
12
18
  };
13
19
  type EventFilter = (eventName: string, params: EventParams) => boolean;
14
20
  type TransformedEvent = {
@@ -17,14 +23,15 @@ type TransformedEvent = {
17
23
  };
18
24
  type EventTransformer = (eventName: string, params: EventParams) => TransformedEvent;
19
25
 
20
- declare const useTracker: () => TrackContextValue;
21
- type TrackRootProps = PropsWithChildren<{
26
+ declare const useReactEventTracking: () => TrackContextValueLegacy;
27
+ type TrackRootProps<CustomHandlers extends Record<string, AnyFunction>> = PropsWithChildren<{
22
28
  onEvent: (eventName: string, params: EventParams) => void;
23
29
  filter?: EventFilter;
24
30
  transform?: EventTransformer;
31
+ customHandlers?: CustomHandlers;
25
32
  }>;
26
- declare const TrackRoot: (({ onEvent, children, filter, transform }: TrackRootProps) => react_jsx_runtime.JSX.Element) & {
27
- factory: (onEvent: (eventName: string, params?: EventParams) => void, filter?: EventFilter, transform?: EventTransformer) => (props: Omit<TrackRootProps, "onEvent" | "filter" | "transform">) => react_jsx_runtime.JSX.Element;
33
+ declare const TrackRoot: (<CustomHandlers extends Record<string, AnyFunction> = {}>({ onEvent, children, filter, transform, customHandlers }: TrackRootProps<CustomHandlers>) => react_jsx_runtime.JSX.Element) & {
34
+ factory: <T extends Record<string, AnyFunction> = {}>(args: TrackRootProps<T>) => (props: Omit<TrackRootProps<T>, "onEvent" | "filter" | "transform" | "customHandlers">) => react_jsx_runtime.JSX.Element;
28
35
  };
29
36
  declare const TrackProvider: <T extends Record<string, any>>({ params, children }: PropsWithChildren<{
30
37
  params: EventParams<T>;
@@ -33,4 +40,13 @@ declare const TrackProvider: <T extends Record<string, any>>({ params, children
33
40
  declare function useMountEvent(eventName: string, params?: EventParams): void;
34
41
  declare function useMountEvent(event: EventObject): void;
35
42
 
36
- export { TrackProvider, TrackRoot, useMountEvent, useTracker };
43
+ declare function createReactEventTrackingHook<Map extends EventsMap, CustomHandlers extends Record<string, AnyFunction> = {}>(): {
44
+ (): {
45
+ track: FlatTracker<Map>;
46
+ } & CustomHandlers;
47
+ <K extends keyof Map>(scope: K): {
48
+ track: FlatTracker<Map>[K];
49
+ } & CustomHandlers;
50
+ };
51
+
52
+ export { TrackProvider, TrackRoot, createReactEventTrackingHook, useMountEvent, useReactEventTracking };
package/dist/index.d.ts CHANGED
@@ -2,13 +2,19 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PropsWithChildren } from 'react';
3
3
 
4
4
  type EventParams<T extends Record<string, any> = Record<string, any>> = T;
5
- interface TrackContextValue {
6
- sendEvent(eventName: string, params?: EventParams): void;
7
- sendEvent(event: EventObject): void;
5
+ type EventsMap = Record<string, any>;
6
+ type AnyFunction = (...args: any[]) => any;
7
+ type IsLeaf<T> = T extends Record<string, any> ? (keyof T extends never ? true : T[keyof T] extends Record<string, any> ? false : true) : true;
8
+ type FlatTracker<T> = {
9
+ [K in keyof T]: IsLeaf<T[K]> extends true ? (params: T[K]) => void : FlatTracker<T[K]>;
10
+ };
11
+ interface TrackContextValueLegacy {
12
+ track(eventName: string, params?: EventParams): void;
13
+ track(event: EventObject): void;
8
14
  }
9
- type EventObject = {
10
- eventName: string;
11
- params?: EventParams;
15
+ type EventObject<E extends string = string, P = EventParams> = {
16
+ eventName: E;
17
+ params?: P;
12
18
  };
13
19
  type EventFilter = (eventName: string, params: EventParams) => boolean;
14
20
  type TransformedEvent = {
@@ -17,14 +23,15 @@ type TransformedEvent = {
17
23
  };
18
24
  type EventTransformer = (eventName: string, params: EventParams) => TransformedEvent;
19
25
 
20
- declare const useTracker: () => TrackContextValue;
21
- type TrackRootProps = PropsWithChildren<{
26
+ declare const useReactEventTracking: () => TrackContextValueLegacy;
27
+ type TrackRootProps<CustomHandlers extends Record<string, AnyFunction>> = PropsWithChildren<{
22
28
  onEvent: (eventName: string, params: EventParams) => void;
23
29
  filter?: EventFilter;
24
30
  transform?: EventTransformer;
31
+ customHandlers?: CustomHandlers;
25
32
  }>;
26
- declare const TrackRoot: (({ onEvent, children, filter, transform }: TrackRootProps) => react_jsx_runtime.JSX.Element) & {
27
- factory: (onEvent: (eventName: string, params?: EventParams) => void, filter?: EventFilter, transform?: EventTransformer) => (props: Omit<TrackRootProps, "onEvent" | "filter" | "transform">) => react_jsx_runtime.JSX.Element;
33
+ declare const TrackRoot: (<CustomHandlers extends Record<string, AnyFunction> = {}>({ onEvent, children, filter, transform, customHandlers }: TrackRootProps<CustomHandlers>) => react_jsx_runtime.JSX.Element) & {
34
+ factory: <T extends Record<string, AnyFunction> = {}>(args: TrackRootProps<T>) => (props: Omit<TrackRootProps<T>, "onEvent" | "filter" | "transform" | "customHandlers">) => react_jsx_runtime.JSX.Element;
28
35
  };
29
36
  declare const TrackProvider: <T extends Record<string, any>>({ params, children }: PropsWithChildren<{
30
37
  params: EventParams<T>;
@@ -33,4 +40,13 @@ declare const TrackProvider: <T extends Record<string, any>>({ params, children
33
40
  declare function useMountEvent(eventName: string, params?: EventParams): void;
34
41
  declare function useMountEvent(event: EventObject): void;
35
42
 
36
- export { TrackProvider, TrackRoot, useMountEvent, useTracker };
43
+ declare function createReactEventTrackingHook<Map extends EventsMap, CustomHandlers extends Record<string, AnyFunction> = {}>(): {
44
+ (): {
45
+ track: FlatTracker<Map>;
46
+ } & CustomHandlers;
47
+ <K extends keyof Map>(scope: K): {
48
+ track: FlatTracker<Map>[K];
49
+ } & CustomHandlers;
50
+ };
51
+
52
+ export { TrackProvider, TrackRoot, createReactEventTrackingHook, useMountEvent, useReactEventTracking };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useContext, useCallback, useMemo, useRef, useEffect } from 'react';
1
+ import React, { useContext, useMemo, useRef, useEffect } from 'react';
2
2
 
3
3
  function parseEventArgs(eventNameOrObject, eventParams) {
4
4
  if (typeof eventNameOrObject === "object") {
@@ -14,20 +14,27 @@ function parseEventArgs(eventNameOrObject, eventParams) {
14
14
  }
15
15
 
16
16
  const TrackContext = React.createContext(null);
17
- const useTracker = () => {
17
+ const useReactEventTracking = () => {
18
18
  const ctx = useContext(TrackContext);
19
19
  if (!ctx) {
20
- throw new Error("useTracker must be used within TrackRoot");
20
+ throw new Error("useReactEventTracking must be used within TrackRoot");
21
21
  }
22
22
  return ctx;
23
23
  };
24
24
  const EmptyParams = {};
25
- const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
25
+ const TrackRootComponent = ({
26
+ onEvent,
27
+ children,
28
+ filter,
29
+ transform,
30
+ customHandlers
31
+ }) => {
26
32
  const parentCtx = useContext(TrackContext);
27
33
  const onEventRef = useFreshRef(onEvent);
28
34
  const filterRef = useFreshRef(filter);
29
35
  const transformRef = useFreshRef(transform);
30
- function sendEvent(eventNameOrObject, eventParams) {
36
+ const customHandlersRef = useFreshRef(customHandlers);
37
+ function track(eventNameOrObject, eventParams) {
31
38
  const { eventName, params: incomingParams } = parseEventArgs(
32
39
  eventNameOrObject,
33
40
  eventParams
@@ -62,20 +69,44 @@ const TrackRootComponent = ({ onEvent, children, filter, transform }) => {
62
69
  }
63
70
  }
64
71
  if (parentCtx) {
65
- parentCtx.sendEvent(eventName, incomingParams);
72
+ parentCtx.track(eventName, incomingParams);
66
73
  }
67
74
  }
68
- const sendEventCached = useCallback(sendEvent, [parentCtx]);
69
- const value = useMemo(() => ({ sendEvent: sendEventCached }), [sendEventCached]);
75
+ const value = useMemo(() => {
76
+ return new Proxy({}, {
77
+ get(_, prop) {
78
+ if (prop === track.name) return track;
79
+ const handler = customHandlersRef.current?.[prop];
80
+ if (typeof handler === "function") {
81
+ return (...args) => handler(...args);
82
+ }
83
+ return void 0;
84
+ },
85
+ has(_, prop) {
86
+ return prop === track.name || (customHandlersRef.current ? prop in customHandlersRef.current : false);
87
+ },
88
+ ownKeys() {
89
+ const customKeys = customHandlersRef.current ? Object.keys(customHandlersRef.current) : [];
90
+ return Array.from(/* @__PURE__ */ new Set([track.name, ...customKeys]));
91
+ },
92
+ getOwnPropertyDescriptor(_, prop) {
93
+ return {
94
+ enumerable: true,
95
+ configurable: true
96
+ };
97
+ }
98
+ });
99
+ }, [parentCtx]);
70
100
  return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
71
101
  };
72
- const factory = (onEvent, filter, transform) => {
102
+ const factory = (args) => {
73
103
  return (props) => /* @__PURE__ */ React.createElement(
74
104
  TrackRootComponent,
75
105
  {
76
- onEvent,
77
- filter,
78
- transform,
106
+ onEvent: args.onEvent,
107
+ filter: args.filter,
108
+ transform: args.transform,
109
+ customHandlers: args.customHandlers,
79
110
  ...props
80
111
  }
81
112
  );
@@ -85,21 +116,20 @@ const TrackProvider = ({
85
116
  params,
86
117
  children
87
118
  }) => {
88
- const ctx = useTracker();
119
+ const ctx = useReactEventTracking();
89
120
  const paramsRef = useFreshRef(params);
90
- function sendEvent(eventNameOrObject, eventParams) {
121
+ function track(eventNameOrObject, eventParams) {
91
122
  const { eventName, params: incomingParams } = parseEventArgs(
92
123
  eventNameOrObject,
93
124
  eventParams
94
125
  );
95
126
  const currentParams = paramsRef.current;
96
- ctx.sendEvent(eventName, {
127
+ ctx.track(eventName, {
97
128
  ...currentParams,
98
129
  ...incomingParams
99
130
  });
100
131
  }
101
- const sendEventCached = useCallback(sendEvent, [ctx]);
102
- const value = useMemo(() => ({ sendEvent: sendEventCached }), [sendEventCached]);
132
+ const value = useMemo(() => ({ ...ctx, track }), [ctx]);
103
133
  return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
104
134
  };
105
135
  function useFreshRef(data) {
@@ -109,7 +139,7 @@ function useFreshRef(data) {
109
139
  }
110
140
 
111
141
  function useMountEvent(eventNameOrObject, eventParams) {
112
- const { sendEvent } = useTracker();
142
+ const { track } = useReactEventTracking();
113
143
  const counterRef = useRef(0);
114
144
  const { eventName, params } = parseEventArgs(eventNameOrObject, eventParams);
115
145
  useEffect(() => {
@@ -117,8 +147,36 @@ function useMountEvent(eventNameOrObject, eventParams) {
117
147
  return;
118
148
  }
119
149
  counterRef.current++;
120
- sendEvent(eventName, params);
150
+ track(eventName, params);
121
151
  }, []);
122
152
  }
123
153
 
124
- export { TrackProvider, TrackRoot, useMountEvent, useTracker };
154
+ function createTracker(path = [], track) {
155
+ return new Proxy(() => {
156
+ }, {
157
+ get(_, prop) {
158
+ return createTracker([...path, prop], track);
159
+ },
160
+ apply(_, __, [params]) {
161
+ const eventName = path.join(".");
162
+ track(eventName, params);
163
+ }
164
+ });
165
+ }
166
+
167
+ function createReactEventTrackingHook() {
168
+ function useTracking(scope) {
169
+ const ctx = useReactEventTracking();
170
+ const tracker = useMemo(() => {
171
+ const prefix = scope ? [String(scope)] : [];
172
+ return {
173
+ ...ctx,
174
+ track: createTracker(prefix, ctx.track)
175
+ };
176
+ }, [ctx, scope]);
177
+ return tracker;
178
+ }
179
+ return useTracking;
180
+ }
181
+
182
+ export { TrackProvider, TrackRoot, createReactEventTrackingHook, useMountEvent, useReactEventTracking };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-event-tracking",
3
- "version": "1.0.9",
3
+ "version": "2.0.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],