react-event-tracking 1.0.0 → 1.0.2
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 +9 -9
- package/dist/index.cjs +52 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.mjs +44 -0
- package/package.json +5 -2
- package/src/index.tsx +0 -70
- package/tests/context.test.tsx +0 -162
- package/tests/setup.ts +0 -6
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -10
package/README.md
CHANGED
|
@@ -19,32 +19,32 @@ yarn add react-event-tracking
|
|
|
19
19
|
|
|
20
20
|
1. Define the root handler (e.g., send to GTM or API)
|
|
21
21
|
```tsx
|
|
22
|
-
import {
|
|
22
|
+
import { TrackRoot } from 'react-event-tracking';
|
|
23
23
|
|
|
24
24
|
const Main = () => (
|
|
25
|
-
<
|
|
25
|
+
<TrackRoot onEvent={(name, params) => gtag('event', name, params)}>
|
|
26
26
|
<App/>
|
|
27
|
-
</
|
|
27
|
+
</TrackRoot>
|
|
28
28
|
);
|
|
29
29
|
```
|
|
30
30
|
2. Wrap any component with shared parameters
|
|
31
31
|
```tsx
|
|
32
|
-
import {
|
|
32
|
+
import { TrackProvider } from 'react-event-tracking';
|
|
33
33
|
|
|
34
34
|
const Dashboard = () => (
|
|
35
|
-
<
|
|
35
|
+
<TrackProvider params={{ screen: 'dashboard' }}>
|
|
36
36
|
<DashboardView/>
|
|
37
|
-
</
|
|
37
|
+
</TrackProvider>
|
|
38
38
|
);
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
3. Send events conveniently. On button click, parameters will be merged.
|
|
42
42
|
|
|
43
43
|
```tsx
|
|
44
|
-
import {
|
|
44
|
+
import { useTracker } from 'react-event-tracking';
|
|
45
45
|
|
|
46
46
|
const MyButton = () => {
|
|
47
|
-
const { sendEvent } =
|
|
47
|
+
const { sendEvent } = useTracker();
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
50
|
// event sent with parameters: { screen: 'dashboard', button_id: '123' }
|
|
@@ -61,7 +61,7 @@ const MyButton = () => {
|
|
|
61
61
|
|
|
62
62
|
```tsx
|
|
63
63
|
export function PageView(props) {
|
|
64
|
-
const { sendEvent } =
|
|
64
|
+
const { sendEvent } = useTracker();
|
|
65
65
|
|
|
66
66
|
useEffect(() => {
|
|
67
67
|
sendEvent('page_view');
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const React = require('react');
|
|
4
|
+
|
|
5
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
|
+
|
|
7
|
+
const React__default = /*#__PURE__*/_interopDefaultCompat(React);
|
|
8
|
+
|
|
9
|
+
const TrackContext = React__default.createContext(null);
|
|
10
|
+
const useTracker = () => {
|
|
11
|
+
const ctx = React.useContext(TrackContext);
|
|
12
|
+
if (!ctx) {
|
|
13
|
+
throw new Error("useTracker must be used within TrackRoot");
|
|
14
|
+
}
|
|
15
|
+
return ctx;
|
|
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
|
+
}, []);
|
|
26
|
+
const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
27
|
+
return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
|
|
28
|
+
};
|
|
29
|
+
const TrackProvider = ({
|
|
30
|
+
params,
|
|
31
|
+
children
|
|
32
|
+
}) => {
|
|
33
|
+
const ctx = useTracker();
|
|
34
|
+
const paramsRef = React.useRef(params);
|
|
35
|
+
paramsRef.current = params;
|
|
36
|
+
const sendEvent = React.useCallback(
|
|
37
|
+
(eventName, eventParams) => {
|
|
38
|
+
const currentParams = paramsRef.current;
|
|
39
|
+
ctx.sendEvent(eventName, {
|
|
40
|
+
...currentParams,
|
|
41
|
+
...eventParams
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
[ctx]
|
|
45
|
+
);
|
|
46
|
+
const value = React.useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
47
|
+
return /* @__PURE__ */ React__default.createElement(TrackContext.Provider, { value }, children);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
exports.TrackProvider = TrackProvider;
|
|
51
|
+
exports.TrackRoot = TrackRoot;
|
|
52
|
+
exports.useTracker = useTracker;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { PropsWithChildren } from 'react';
|
|
3
|
+
|
|
4
|
+
type EventParams = Record<string, any>;
|
|
5
|
+
interface TrackContextValue {
|
|
6
|
+
sendEvent: (eventName: string, params?: EventParams) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const useTracker: () => TrackContextValue;
|
|
9
|
+
declare const TrackRoot: ({ onEvent, children }: PropsWithChildren<{
|
|
10
|
+
onEvent: (eventName: string, params?: EventParams) => void;
|
|
11
|
+
}>) => react_jsx_runtime.JSX.Element;
|
|
12
|
+
declare const TrackProvider: ({ params, children }: PropsWithChildren<{
|
|
13
|
+
params: EventParams;
|
|
14
|
+
}>) => react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { TrackProvider, TrackRoot, useTracker };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { PropsWithChildren } from 'react';
|
|
3
|
+
|
|
4
|
+
type EventParams = Record<string, any>;
|
|
5
|
+
interface TrackContextValue {
|
|
6
|
+
sendEvent: (eventName: string, params?: EventParams) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const useTracker: () => TrackContextValue;
|
|
9
|
+
declare const TrackRoot: ({ onEvent, children }: PropsWithChildren<{
|
|
10
|
+
onEvent: (eventName: string, params?: EventParams) => void;
|
|
11
|
+
}>) => react_jsx_runtime.JSX.Element;
|
|
12
|
+
declare const TrackProvider: ({ params, children }: PropsWithChildren<{
|
|
13
|
+
params: EventParams;
|
|
14
|
+
}>) => react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { TrackProvider, TrackRoot, useTracker };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { PropsWithChildren } from 'react';
|
|
3
|
+
|
|
4
|
+
type EventParams = Record<string, any>;
|
|
5
|
+
interface TrackContextValue {
|
|
6
|
+
sendEvent: (eventName: string, params?: EventParams) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const useTracker: () => TrackContextValue;
|
|
9
|
+
declare const TrackRoot: ({ onEvent, children }: PropsWithChildren<{
|
|
10
|
+
onEvent: (eventName: string, params?: EventParams) => void;
|
|
11
|
+
}>) => react_jsx_runtime.JSX.Element;
|
|
12
|
+
declare const TrackProvider: ({ params, children }: PropsWithChildren<{
|
|
13
|
+
params: EventParams;
|
|
14
|
+
}>) => react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { TrackProvider, TrackRoot, useTracker };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useContext, useRef, useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
const TrackContext = React.createContext(null);
|
|
4
|
+
const useTracker = () => {
|
|
5
|
+
const ctx = useContext(TrackContext);
|
|
6
|
+
if (!ctx) {
|
|
7
|
+
throw new Error("useTracker must be used within TrackRoot");
|
|
8
|
+
}
|
|
9
|
+
return ctx;
|
|
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
|
+
}, []);
|
|
20
|
+
const value = useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
21
|
+
return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
|
|
22
|
+
};
|
|
23
|
+
const TrackProvider = ({
|
|
24
|
+
params,
|
|
25
|
+
children
|
|
26
|
+
}) => {
|
|
27
|
+
const ctx = useTracker();
|
|
28
|
+
const paramsRef = useRef(params);
|
|
29
|
+
paramsRef.current = params;
|
|
30
|
+
const sendEvent = useCallback(
|
|
31
|
+
(eventName, eventParams) => {
|
|
32
|
+
const currentParams = paramsRef.current;
|
|
33
|
+
ctx.sendEvent(eventName, {
|
|
34
|
+
...currentParams,
|
|
35
|
+
...eventParams
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
[ctx]
|
|
39
|
+
);
|
|
40
|
+
const value = useMemo(() => ({ sendEvent }), [sendEvent]);
|
|
41
|
+
return /* @__PURE__ */ React.createElement(TrackContext.Provider, { value }, children);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { TrackProvider, TrackRoot, useTracker };
|
package/package.json
CHANGED
package/src/index.tsx
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useCallback,
|
|
3
|
-
useContext,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
type PropsWithChildren
|
|
7
|
-
} from "react"
|
|
8
|
-
|
|
9
|
-
export type AnalyticsParams = Record<string, any>
|
|
10
|
-
|
|
11
|
-
interface AnalyticsContextValue {
|
|
12
|
-
sendEvent: (eventName: string, params?: AnalyticsParams) => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const AnalyticsContext = React.createContext<AnalyticsContextValue | null>(null)
|
|
16
|
-
|
|
17
|
-
export const useAnalytics = () => {
|
|
18
|
-
const ctx = useContext(AnalyticsContext)
|
|
19
|
-
if (!ctx) {
|
|
20
|
-
throw new Error("useAnalytics must be used within AnalyticsRoot")
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return ctx
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const AnalyticsRoot = ({
|
|
27
|
-
onEvent,
|
|
28
|
-
children
|
|
29
|
-
}: PropsWithChildren<{
|
|
30
|
-
onEvent: (eventName: string, params?: AnalyticsParams) => void
|
|
31
|
-
}>) => {
|
|
32
|
-
const onEventRef = useRef(onEvent)
|
|
33
|
-
onEventRef.current = onEvent
|
|
34
|
-
|
|
35
|
-
const sendEvent = useCallback((eventName: string, params?: AnalyticsParams) => {
|
|
36
|
-
onEventRef.current(eventName, params)
|
|
37
|
-
}, [])
|
|
38
|
-
|
|
39
|
-
const value = useMemo(() => ({ sendEvent }), [sendEvent])
|
|
40
|
-
|
|
41
|
-
return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const AnalyticsProvider = ({
|
|
45
|
-
params,
|
|
46
|
-
children
|
|
47
|
-
}: PropsWithChildren<{
|
|
48
|
-
params: AnalyticsParams
|
|
49
|
-
}>) => {
|
|
50
|
-
const ctx = useAnalytics()
|
|
51
|
-
|
|
52
|
-
const paramsRef = useRef(params)
|
|
53
|
-
paramsRef.current = params
|
|
54
|
-
|
|
55
|
-
const sendEvent = useCallback(
|
|
56
|
-
(eventName: string, eventParams?: AnalyticsParams) => {
|
|
57
|
-
const currentParams = paramsRef.current
|
|
58
|
-
|
|
59
|
-
ctx.sendEvent(eventName, {
|
|
60
|
-
...currentParams,
|
|
61
|
-
...eventParams
|
|
62
|
-
})
|
|
63
|
-
},
|
|
64
|
-
[ctx]
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const value = useMemo(() => ({ sendEvent }), [sendEvent])
|
|
68
|
-
|
|
69
|
-
return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>
|
|
70
|
-
}
|
package/tests/context.test.tsx
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest"
|
|
2
|
-
import React from "react"
|
|
3
|
-
import { render, screen } from "@testing-library/react"
|
|
4
|
-
import userEvent from "@testing-library/user-event"
|
|
5
|
-
import { AnalyticsRoot, AnalyticsProvider, useAnalytics } from "../src"
|
|
6
|
-
|
|
7
|
-
const TestButton = ({
|
|
8
|
-
eventName,
|
|
9
|
-
params,
|
|
10
|
-
label = "Click me"
|
|
11
|
-
}: {
|
|
12
|
-
eventName: string
|
|
13
|
-
params?: Record<string, any>
|
|
14
|
-
label?: string
|
|
15
|
-
}) => {
|
|
16
|
-
const { sendEvent } = useAnalytics()
|
|
17
|
-
return <button onClick={() => sendEvent(eventName, params)}>{label}</button>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("Analytics Context", () => {
|
|
21
|
-
it("should send event from root", async () => {
|
|
22
|
-
const onEvent = vi.fn()
|
|
23
|
-
|
|
24
|
-
render(
|
|
25
|
-
<AnalyticsRoot onEvent={onEvent}>
|
|
26
|
-
<TestButton
|
|
27
|
-
eventName="test_click"
|
|
28
|
-
params={{ foo: "bar" }}
|
|
29
|
-
label="Root Click"
|
|
30
|
-
/>
|
|
31
|
-
</AnalyticsRoot>
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
await userEvent.click(screen.getByText("Root Click"))
|
|
35
|
-
|
|
36
|
-
expect(onEvent).toHaveBeenCalledTimes(1)
|
|
37
|
-
expect(onEvent).toHaveBeenCalledWith("test_click", { foo: "bar" })
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it("should merge params from nested providers", async () => {
|
|
41
|
-
const onEvent = vi.fn()
|
|
42
|
-
|
|
43
|
-
render(
|
|
44
|
-
<AnalyticsRoot onEvent={onEvent}>
|
|
45
|
-
<AnalyticsProvider params={{ section: "header" }}>
|
|
46
|
-
<AnalyticsProvider params={{ item: "logo" }}>
|
|
47
|
-
<TestButton
|
|
48
|
-
eventName="logo_click"
|
|
49
|
-
params={{ action: "click" }}
|
|
50
|
-
label="Nested Click"
|
|
51
|
-
/>
|
|
52
|
-
</AnalyticsProvider>
|
|
53
|
-
</AnalyticsProvider>
|
|
54
|
-
</AnalyticsRoot>
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
await userEvent.click(screen.getByText("Nested Click"))
|
|
58
|
-
|
|
59
|
-
expect(onEvent).toHaveBeenCalledWith("logo_click", {
|
|
60
|
-
section: "header",
|
|
61
|
-
item: "logo",
|
|
62
|
-
action: "click"
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it("should override params from child to parent", async () => {
|
|
67
|
-
const onEvent = vi.fn()
|
|
68
|
-
|
|
69
|
-
render(
|
|
70
|
-
<AnalyticsRoot onEvent={onEvent}>
|
|
71
|
-
<AnalyticsProvider params={{ page: "home", id: 1 }}>
|
|
72
|
-
{/* Переопределяем id */}
|
|
73
|
-
<AnalyticsProvider params={{ id: 2 }}>
|
|
74
|
-
<TestButton
|
|
75
|
-
eventName="click"
|
|
76
|
-
params={{ id: 3 }}
|
|
77
|
-
label="Override Click"
|
|
78
|
-
/>
|
|
79
|
-
</AnalyticsProvider>
|
|
80
|
-
</AnalyticsProvider>
|
|
81
|
-
</AnalyticsRoot>
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
await userEvent.click(screen.getByText("Override Click"))
|
|
85
|
-
|
|
86
|
-
expect(onEvent).toHaveBeenCalledWith("click", {
|
|
87
|
-
page: "home",
|
|
88
|
-
id: 3
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it("should use latest params without re-rendering children", async () => {
|
|
93
|
-
const onEvent = vi.fn()
|
|
94
|
-
|
|
95
|
-
const Wrapper = ({ count }: { count: number }) => (
|
|
96
|
-
<AnalyticsProvider params={{ count }}>
|
|
97
|
-
<TestButton eventName="count_click" label="Rerender Click" />
|
|
98
|
-
</AnalyticsProvider>
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
const { rerender } = render(
|
|
102
|
-
<AnalyticsRoot onEvent={onEvent}>
|
|
103
|
-
<Wrapper count={1} />
|
|
104
|
-
</AnalyticsRoot>
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
await userEvent.click(screen.getByText("Rerender Click"))
|
|
108
|
-
expect(onEvent).toHaveBeenLastCalledWith("count_click", { count: 1 })
|
|
109
|
-
|
|
110
|
-
rerender(
|
|
111
|
-
<AnalyticsRoot onEvent={onEvent}>
|
|
112
|
-
<Wrapper count={2} />
|
|
113
|
-
</AnalyticsRoot>
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
await userEvent.click(screen.getByText("Rerender Click"))
|
|
117
|
-
expect(onEvent).toHaveBeenLastCalledWith("count_click", { count: 2 })
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it("should NOT trigger re-render in consumers when params update (optimization check)", () => {
|
|
121
|
-
const renderFn = vi.fn()
|
|
122
|
-
|
|
123
|
-
const MemoChild = React.memo(() => {
|
|
124
|
-
useAnalytics()
|
|
125
|
-
renderFn()
|
|
126
|
-
return <div>Memo Child</div>
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// eslint-disable-next-line react/display-name
|
|
130
|
-
MemoChild.displayName = "MemoChild"
|
|
131
|
-
|
|
132
|
-
const { rerender } = render(
|
|
133
|
-
<AnalyticsRoot onEvent={() => {}}>
|
|
134
|
-
<AnalyticsProvider params={{ val: 1 }}>
|
|
135
|
-
<MemoChild />
|
|
136
|
-
</AnalyticsProvider>
|
|
137
|
-
</AnalyticsRoot>
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
expect(renderFn).toHaveBeenCalledTimes(1)
|
|
141
|
-
|
|
142
|
-
rerender(
|
|
143
|
-
<AnalyticsRoot onEvent={() => {}}>
|
|
144
|
-
<AnalyticsProvider params={{ val: 2 }}>
|
|
145
|
-
<MemoChild />
|
|
146
|
-
</AnalyticsProvider>
|
|
147
|
-
</AnalyticsRoot>
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
expect(renderFn).toHaveBeenCalledTimes(1)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it("should throw error if used outside of AnalyticsRoot", () => {
|
|
154
|
-
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
|
155
|
-
|
|
156
|
-
expect(() => {
|
|
157
|
-
render(<TestButton eventName="fail" label="Fail Click" />)
|
|
158
|
-
}).toThrow("useAnalytics must be used within AnalyticsRoot")
|
|
159
|
-
|
|
160
|
-
consoleSpy.mockRestore()
|
|
161
|
-
})
|
|
162
|
-
})
|
package/tests/setup.ts
DELETED
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"jsx": "react-jsx",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"declarationMap": true,
|
|
8
|
-
"outDir": "./dist",
|
|
9
|
-
"rootDir": "./src",
|
|
10
|
-
"strict": true,
|
|
11
|
-
"moduleResolution": "node",
|
|
12
|
-
"esModuleInterop": true,
|
|
13
|
-
"skipLibCheck": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|