use-page-view 1.0.0 → 1.0.1
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 +176 -100
- package/package.json +19 -6
package/README.md
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# use-page-view
|
2
2
|
|
3
|
-
A React hook for tracking page views and user engagement time. This hook provides real-time
|
3
|
+
A React hook for tracking page views and user engagement time. This hook provides real-time tracking of how long users spend on a page, their activity status, and flexible callbacks for implementing custom persistence and analytics.
|
4
4
|
|
5
5
|
[](https://www.npmjs.com/package/use-page-view)
|
6
6
|
[](https://www.npmjs.com/package/use-page-view)
|
7
|
+
[](https://bundlephobia.com/package/use-page-view)
|
7
8
|
[](https://github.com/christophersesugh/use-page-view/blob/main/LICENSE)
|
8
9
|
[](https://github.com/christophersesugh/use-page-view/actions)
|
9
10
|
[](https://codecov.io/gh/christophersesugh/use-page-view)
|
@@ -12,10 +13,13 @@ A React hook for tracking page views and user engagement time. This hook provide
|
|
12
13
|
|
13
14
|
- 📊 Track time spent on pages
|
14
15
|
- 👀 Monitor user activity and page visibility
|
15
|
-
-
|
16
|
-
-
|
17
|
-
- 🎯 Support for
|
18
|
-
-
|
16
|
+
- 🔄 Periodic updates through callback
|
17
|
+
- ⏱️ Configurable thresholds and intervals
|
18
|
+
- 🎯 Support for one-time tracking
|
19
|
+
- 🔑 User identification support
|
20
|
+
- 🚀 Lightweight with minimal overhead
|
21
|
+
- 🌐 SSR/React Router/Next.js compatible
|
22
|
+
- 🛠️ Flexible - implement your own persistence strategy
|
19
23
|
|
20
24
|
## Installation
|
21
25
|
|
@@ -27,7 +31,37 @@ yarn add use-page-view
|
|
27
31
|
pnpm add use-page-view
|
28
32
|
```
|
29
33
|
|
30
|
-
##
|
34
|
+
## Quick Start
|
35
|
+
|
36
|
+
```tsx
|
37
|
+
import { usePageView } from 'use-page-view';
|
38
|
+
|
39
|
+
function MyPage() {
|
40
|
+
const { timeSpent, isActive } = usePageView({
|
41
|
+
pageId: 'my-page',
|
42
|
+
onPageView: (data) => console.log('Page view:', data),
|
43
|
+
});
|
44
|
+
|
45
|
+
return (
|
46
|
+
<div>
|
47
|
+
<h1>My Page</h1>
|
48
|
+
<p>
|
49
|
+
Time spent: {timeSpent}s {isActive ? '🟢 Active' : '🔴 Inactive'}
|
50
|
+
</p>
|
51
|
+
</div>
|
52
|
+
);
|
53
|
+
}
|
54
|
+
```
|
55
|
+
|
56
|
+
## Compatibility
|
57
|
+
|
58
|
+
- **React**: 16.8+ (hooks support required)
|
59
|
+
- **TypeScript**: Full TypeScript support included
|
60
|
+
- **Browsers**: Modern browsers
|
61
|
+
- **SSR**: Compatible with React Router, Next.js and other SSR frameworks
|
62
|
+
- **React Native**: Not supported (web-only)
|
63
|
+
|
64
|
+
## Usage
|
31
65
|
|
32
66
|
```tsx
|
33
67
|
import { usePageView } from 'use-page-view';
|
@@ -35,170 +69,212 @@ import { usePageView } from 'use-page-view';
|
|
35
69
|
function BlogPost() {
|
36
70
|
const { timeSpent, isActive } = usePageView({
|
37
71
|
pageId: 'blog-post-123',
|
38
|
-
|
39
|
-
|
40
|
-
|
72
|
+
userId: 'user-456', // Optional
|
73
|
+
minTimeThreshold: 10, // Minimum time before recording (seconds)
|
74
|
+
heartbeatInterval: 30, // How often to send updates (seconds)
|
75
|
+
inactivityThreshold: 60, // Time before user is considered inactive (seconds)
|
76
|
+
onPageView: async (data) => {
|
77
|
+
try {
|
78
|
+
await fetch('/api/track-page-view', {
|
79
|
+
method: 'POST',
|
80
|
+
headers: { 'Content-Type': 'application/json' },
|
81
|
+
body: JSON.stringify(data),
|
82
|
+
});
|
83
|
+
} catch (error) {
|
84
|
+
console.error('Failed to track page view:', error);
|
85
|
+
}
|
41
86
|
},
|
42
87
|
});
|
43
88
|
|
44
89
|
return (
|
45
90
|
<div>
|
46
91
|
<div>
|
47
|
-
Time
|
92
|
+
Time: {formatTime(timeSpent)} {isActive ? '🟢' : '🔴'}
|
48
93
|
</div>
|
49
94
|
<article>Your content here...</article>
|
50
95
|
</div>
|
51
96
|
);
|
52
97
|
}
|
53
|
-
```
|
54
98
|
|
55
|
-
|
99
|
+
// Helper function to format time
|
100
|
+
function formatTime(seconds: number): string {
|
101
|
+
const mins = Math.floor(seconds / 60);
|
102
|
+
const secs = seconds % 60;
|
103
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
104
|
+
}
|
105
|
+
```
|
56
106
|
|
57
|
-
|
107
|
+
## API
|
58
108
|
|
59
|
-
|
109
|
+
### usePageView(options)
|
60
110
|
|
61
|
-
|
62
|
-
| --------------------- | ------------------------------ | -------- | ------- | ---------------------------------------------------------------------- |
|
63
|
-
| `pageId` | `string` | Yes | - | Unique identifier for the page being tracked |
|
64
|
-
| `userId` | `string` | No | - | Optional user identifier if user is logged in |
|
65
|
-
| `minTimeThreshold` | `number` | No | `5` | Minimum time in seconds before recording a view |
|
66
|
-
| `heartbeatInterval` | `number` | No | `30` | How often to send updates in seconds |
|
67
|
-
| `inactivityThreshold` | `number` | No | `30` | Time in seconds before user is considered inactive |
|
68
|
-
| `onPageView` | `(data: PageViewData) => void` | No | - | Callback function to handle page view data |
|
69
|
-
| `trackOnce` | `boolean` | No | `false` | Track only the initial view |
|
70
|
-
| `trackOnceDelay` | `number` | No | `0` | Minimum time in seconds before recording a view when trackOnce is true |
|
111
|
+
#### Options
|
71
112
|
|
72
|
-
|
113
|
+
| Option | Type | Default | Description |
|
114
|
+
| --------------------- | ------------------------------ | -------- | ---------------------------------------------------------------------- |
|
115
|
+
| `pageId` | `string` | Required | Unique identifier for the page being tracked |
|
116
|
+
| `userId` | `string` | Optional | User identifier if user is logged in |
|
117
|
+
| `minTimeThreshold` | `number` | `5` | Minimum time in seconds before recording a view |
|
118
|
+
| `heartbeatInterval` | `number` | `30` | How often to send updates in seconds |
|
119
|
+
| `inactivityThreshold` | `number` | `30` | Time in seconds before user is considered inactive |
|
120
|
+
| `onPageView` | `(data: PageViewData) => void` | Optional | Callback function to handle page view data |
|
121
|
+
| `trackOnce` | `boolean` | `false` | Track only the initial view |
|
122
|
+
| `trackOnceDelay` | `number` | `0` | Minimum time in seconds before recording a view when trackOnce is true |
|
73
123
|
|
74
|
-
|
124
|
+
#### Returns
|
75
125
|
|
76
126
|
| Property | Type | Description |
|
77
127
|
| ----------- | --------- | ------------------------------------------------ |
|
78
128
|
| `timeSpent` | `number` | Total time spent on the page in seconds |
|
79
129
|
| `isActive` | `boolean` | Whether the user is currently active on the page |
|
80
130
|
|
81
|
-
### PageViewData
|
82
|
-
|
83
|
-
The `onPageView` callback receives a `PageViewData` object with the following structure:
|
131
|
+
### PageViewData
|
84
132
|
|
85
133
|
```typescript
|
86
134
|
interface PageViewData {
|
87
|
-
pageId: string;
|
88
|
-
userId?: string;
|
89
|
-
timeSpent: number;
|
90
|
-
isActive: boolean;
|
135
|
+
pageId: string;
|
136
|
+
userId?: string;
|
137
|
+
timeSpent: number;
|
138
|
+
isActive: boolean;
|
91
139
|
}
|
92
140
|
```
|
93
141
|
|
94
|
-
##
|
95
|
-
|
96
|
-
### Tracking User-Specific Views
|
97
|
-
|
98
|
-
```tsx
|
99
|
-
function UserProfile() {
|
100
|
-
const { timeSpent, isActive } = usePageView({
|
101
|
-
pageId: 'user-profile',
|
102
|
-
userId: 'user-123', // Track for specific user
|
103
|
-
onPageView: (data) => {
|
104
|
-
// Send to your analytics service
|
105
|
-
analytics.trackPageView(data);
|
106
|
-
},
|
107
|
-
});
|
108
|
-
}
|
109
|
-
```
|
142
|
+
## Examples
|
110
143
|
|
111
|
-
###
|
144
|
+
### Basic Usage
|
112
145
|
|
113
146
|
```tsx
|
114
147
|
function Article() {
|
115
148
|
const { timeSpent, isActive } = usePageView({
|
116
|
-
pageId: 'article-
|
117
|
-
minTimeThreshold: 10, // Only track after 10 seconds
|
118
|
-
heartbeatInterval: 60, // Send updates every minute
|
119
|
-
inactivityThreshold: 120, // Consider user inactive after 2 minutes
|
149
|
+
pageId: 'article-789',
|
120
150
|
onPageView: (data) => {
|
121
|
-
|
151
|
+
console.log('Article view:', data);
|
122
152
|
},
|
123
153
|
});
|
154
|
+
|
155
|
+
return (
|
156
|
+
<div>
|
157
|
+
<h1>Article Title</h1>
|
158
|
+
<div>Time spent: {formatTime(timeSpent)}</div>
|
159
|
+
<p>Article content...</p>
|
160
|
+
</div>
|
161
|
+
);
|
124
162
|
}
|
125
163
|
```
|
126
164
|
|
127
|
-
### One-
|
165
|
+
### One-time Tracking
|
128
166
|
|
129
167
|
```tsx
|
130
168
|
function LandingPage() {
|
131
169
|
const { timeSpent, isActive } = usePageView({
|
132
170
|
pageId: 'landing-page',
|
133
|
-
trackOnce: true,
|
134
|
-
trackOnceDelay:
|
171
|
+
trackOnce: true,
|
172
|
+
trackOnceDelay: 30, // Track after 30 seconds
|
135
173
|
onPageView: (data) => {
|
136
|
-
|
174
|
+
analytics.track('landing_page_view', data);
|
137
175
|
},
|
138
176
|
});
|
177
|
+
|
178
|
+
return (
|
179
|
+
<div>
|
180
|
+
<h1>Welcome</h1>
|
181
|
+
<div>Time on page: {formatTime(timeSpent)}</div>
|
182
|
+
</div>
|
183
|
+
);
|
139
184
|
}
|
140
185
|
```
|
141
186
|
|
142
|
-
###
|
187
|
+
### Error Handling
|
143
188
|
|
144
189
|
```tsx
|
145
|
-
function
|
146
|
-
const mins = Math.floor(seconds / 60);
|
147
|
-
const secs = seconds % 60;
|
148
|
-
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
149
|
-
}
|
150
|
-
|
151
|
-
function BlogPost() {
|
190
|
+
function PageWithErrorHandling() {
|
152
191
|
const { timeSpent, isActive } = usePageView({
|
153
|
-
pageId: '
|
154
|
-
onPageView: (data) => {
|
155
|
-
|
192
|
+
pageId: 'important-page',
|
193
|
+
onPageView: async (data) => {
|
194
|
+
try {
|
195
|
+
const response = await fetch('/api/analytics', {
|
196
|
+
method: 'POST',
|
197
|
+
headers: { 'Content-Type': 'application/json' },
|
198
|
+
body: JSON.stringify(data),
|
199
|
+
});
|
200
|
+
|
201
|
+
if (!response.ok) {
|
202
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
203
|
+
}
|
204
|
+
} catch (error) {
|
205
|
+
console.error('Analytics tracking failed:', error);
|
206
|
+
// Optionally queue for retry or use alternative tracking
|
207
|
+
}
|
156
208
|
},
|
157
209
|
});
|
158
210
|
|
159
|
-
return
|
160
|
-
<div>
|
161
|
-
<div>
|
162
|
-
Time: {formatTime(timeSpent)} {isActive ? '🟢' : '🔴'}
|
163
|
-
</div>
|
164
|
-
</div>
|
165
|
-
);
|
211
|
+
return <div>Your page content</div>;
|
166
212
|
}
|
167
213
|
```
|
168
214
|
|
169
|
-
##
|
215
|
+
## Features in Detail
|
170
216
|
|
171
|
-
|
217
|
+
### Time Tracking
|
172
218
|
|
173
|
-
|
219
|
+
- Tracks total time spent on the page
|
220
|
+
- Updates every second
|
221
|
+
- Handles browser tab switching and page visibility changes
|
222
|
+
- Supports custom initial time values for persistence scenarios
|
174
223
|
|
175
|
-
|
224
|
+
### Activity Monitoring
|
176
225
|
|
177
|
-
|
178
|
-
|
226
|
+
- Detects user activity through mouse, keyboard, and touch events
|
227
|
+
- Monitors page visibility changes
|
228
|
+
- Updates active status based on user engagement
|
229
|
+
- Configurable inactivity threshold
|
179
230
|
|
180
|
-
|
231
|
+
### Flexible Persistence
|
181
232
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
233
|
+
- No built-in persistence to keep the library lightweight
|
234
|
+
- Easy to implement custom persistence strategies
|
235
|
+
|
236
|
+
### Callback System
|
237
|
+
|
238
|
+
- Periodic updates through `onPageView` callback
|
239
|
+
- Configurable update interval with `heartbeatInterval`
|
240
|
+
- Minimum time threshold with `minTimeThreshold`
|
241
|
+
- One-time tracking option with `trackOnce`
|
242
|
+
|
243
|
+
## Performance
|
244
|
+
|
245
|
+
- **Minimal overhead**: Efficient event handling with automatic cleanup
|
246
|
+
- **Memory efficient**: Uses `requestIdleCallback` when available
|
247
|
+
- **Bundle size**: < 5KB minified + gzipped
|
248
|
+
- **No dependencies**: Zero external dependencies for maximum compatibility
|
249
|
+
- **Automatic cleanup**: All event listeners and timers are cleaned up on unmount
|
250
|
+
|
251
|
+
## FAQ
|
252
|
+
|
253
|
+
**Q: Does this work with SSR/React Router/Next.js?**
|
254
|
+
A: Yes, the hook safely handles server-side rendering by checking for browser environment before initializing.
|
255
|
+
|
256
|
+
**Q: How do I persist time tracking across page reloads?**
|
257
|
+
A: Implement your own persistence strategy using the examples above. You can use localStorage, sessionStorage, databases, or any other storage method that fits your needs.
|
258
|
+
|
259
|
+
**Q: How accurate is the time tracking?**
|
260
|
+
A: Time tracking is updated every second and accounts for page visibility changes and user inactivity.
|
261
|
+
|
262
|
+
**Q: Can I track multiple pages in the same app?**
|
263
|
+
A: Yes, use different `pageId` values for each page or component you want to track separately.
|
264
|
+
|
265
|
+
**Q: Does this affect my app's performance?**
|
266
|
+
A: No, the hook uses efficient event handling and cleanup. The bundle size is minimal (< 3KB).
|
192
267
|
|
193
|
-
|
268
|
+
**Q: What events are considered "activity"?**
|
269
|
+
A: Mouse movement, clicks, keyboard input, touch events, and scroll events are all considered user activity.
|
194
270
|
|
195
|
-
|
271
|
+
**Q: Why doesn't the hook include built-in localStorage support?**
|
272
|
+
A: To keep the library lightweight and flexible. Different applications have different persistence needs, and this approach allows you to implement exactly what you need without bloating the core library.
|
196
273
|
|
197
|
-
|
198
|
-
- `window.addEventListener` for user activity events
|
274
|
+
## Contributing
|
199
275
|
|
200
|
-
|
276
|
+
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
201
277
|
|
202
278
|
## License
|
203
279
|
|
204
|
-
MIT
|
280
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "use-page-view",
|
3
|
-
"version": "1.0.
|
4
|
-
"description": "React hook for tracking page views",
|
3
|
+
"version": "1.0.1",
|
4
|
+
"description": "A React hook for tracking page views and user engagement time. This hook provides real-time tracking of how long users spend on a page, their activity status, and the ability to persist time tracking across page reloads.",
|
5
5
|
"type": "module",
|
6
6
|
"main": "dist/index.js",
|
7
7
|
"files": [
|
@@ -9,8 +9,19 @@
|
|
9
9
|
],
|
10
10
|
"keywords": [
|
11
11
|
"react",
|
12
|
+
"hooks",
|
12
13
|
"page-view",
|
13
|
-
"
|
14
|
+
"analytics",
|
15
|
+
"tracking",
|
16
|
+
"engagement",
|
17
|
+
"time-tracking",
|
18
|
+
"localstorage",
|
19
|
+
"persistence",
|
20
|
+
"typescript",
|
21
|
+
"react-hooks",
|
22
|
+
"user-activity",
|
23
|
+
"page-visibility",
|
24
|
+
"performance-monitoring"
|
14
25
|
],
|
15
26
|
"author": "Christopher S. Aondona <me@codingsimba.com> (https://codingsimba.com)",
|
16
27
|
"repository": {
|
@@ -23,16 +34,17 @@
|
|
23
34
|
},
|
24
35
|
"scripts": {
|
25
36
|
"build": "tsdown",
|
26
|
-
"test:watch": "vitest",
|
27
37
|
"test": "vitest run",
|
38
|
+
"test:watch": "vitest",
|
39
|
+
"test:coverage": "vitest run --coverage",
|
28
40
|
"lint": "tsc",
|
29
41
|
"format": "prettier --write .",
|
30
42
|
"format:check": "prettier . --check",
|
31
43
|
"exports:check": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
|
32
44
|
"release:version": "changeset version",
|
33
|
-
"release:local": "changeset version && changeset publish",
|
45
|
+
"release:local": "changeset && changeset version && changeset publish",
|
34
46
|
"prepublishOnly": "npm run ci",
|
35
|
-
"ci": "npm run build && npm run format:check && npm run lint && npm run test"
|
47
|
+
"ci": "npm run build && npm run format:check && npm run lint && npm run exports:check && npm run test"
|
36
48
|
},
|
37
49
|
"license": "MIT",
|
38
50
|
"dependencies": {
|
@@ -48,6 +60,7 @@
|
|
48
60
|
"@types/react-dom": "^19.1.0",
|
49
61
|
"@vitejs/plugin-react": "^4.5.1",
|
50
62
|
"@vitest/browser": "^3.2.0",
|
63
|
+
"@vitest/coverage-v8": "^3.2.2",
|
51
64
|
"playwright": "^1.52.0",
|
52
65
|
"prettier": "^3.5.3",
|
53
66
|
"tsdown": "^0.12.6",
|