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.
Files changed (2) hide show
  1. package/README.md +176 -100
  2. 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 monitoring of user activity, page visibility, and time spent on pages.
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
  [![npm version](https://img.shields.io/npm/v/use-page-view.svg)](https://www.npmjs.com/package/use-page-view)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/use-page-view.svg)](https://www.npmjs.com/package/use-page-view)
7
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/use-page-view)](https://bundlephobia.com/package/use-page-view)
7
8
  [![License](https://img.shields.io/npm/l/use-page-view.svg)](https://github.com/christophersesugh/use-page-view/blob/main/LICENSE)
8
9
  [![CI Status](https://img.shields.io/github/actions/workflow/status/christophersesugh/use-page-view/ci.yml)](https://github.com/christophersesugh/use-page-view/actions)
9
10
  [![Coverage](https://img.shields.io/codecov/c/github/christophersesugh/use-page-view)](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
- - ⏱️ Configurable tracking intervals
16
- - 🔄 Real-time updates
17
- - 🎯 Support for both continuous and one-time tracking
18
- - 🛡️ TypeScript support
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
- ## Basic Usage
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
- onPageView: (data) => {
39
- // Handle page view data
40
- console.log(data);
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 spent: {timeSpent}s {isActive ? '🟢' : '🔴'}
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
- ## API Reference
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
- ### Parameters
107
+ ## API
58
108
 
59
- The `usePageView` hook accepts an options object with the following properties:
109
+ ### usePageView(options)
60
110
 
61
- | Parameter | Type | Required | Default | Description |
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
- ### Return Value
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
- The hook returns an object with the following properties:
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 Interface
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; // The page identifier
88
- userId?: string; // Optional user identifier
89
- timeSpent: number; // Time spent in seconds
90
- isActive: boolean; // Whether the user is active
135
+ pageId: string;
136
+ userId?: string;
137
+ timeSpent: number;
138
+ isActive: boolean;
91
139
  }
92
140
  ```
93
141
 
94
- ## Advanced Usage
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
- ### Custom Tracking Intervals
144
+ ### Basic Usage
112
145
 
113
146
  ```tsx
114
147
  function Article() {
115
148
  const { timeSpent, isActive } = usePageView({
116
- pageId: 'article-456',
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
- // Handle page view data
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-Time Tracking
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, // Only track the initial view
134
- trackOnceDelay: 5, // Wait 5 seconds before tracking
171
+ trackOnce: true,
172
+ trackOnceDelay: 30, // Track after 30 seconds
135
173
  onPageView: (data) => {
136
- // Handle initial page view
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
- ### Custom Time Formatting
187
+ ### Error Handling
143
188
 
144
189
  ```tsx
145
- function formatTime(seconds: number): string {
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: 'blog-post-123',
154
- onPageView: (data) => {
155
- // Handle page view data
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
- ## Best Practices
215
+ ## Features in Detail
170
216
 
171
- 1. **Unique Page IDs**: Always use unique identifiers for each page to ensure accurate tracking.
217
+ ### Time Tracking
172
218
 
173
- 2. **User Identification**: Include `userId` when tracking authenticated users for better analytics.
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
- 3. **Performance**:
224
+ ### Activity Monitoring
176
225
 
177
- - Use appropriate `heartbeatInterval` values to balance real-time updates with performance
178
- - Consider using `trackOnce` for pages where continuous tracking isn't needed
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
- 4. **Error Handling**: Always implement error handling in your `onPageView` callback:
231
+ ### Flexible Persistence
181
232
 
182
- ```tsx
183
- const handlePageView = async (data: PageViewData) => {
184
- try {
185
- await analytics.trackPageView(data);
186
- } catch (error) {
187
- console.error('Failed to track page view:', error);
188
- // Implement fallback or retry logic
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
- ## Browser Support
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
- The hook uses the following browser APIs:
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
- - `document.visibilitychange`
198
- - `window.addEventListener` for user activity events
274
+ ## Contributing
199
275
 
200
- It's compatible with all modern browsers that support these features.
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 © [Christopher S. Aondona](https://codingsimba.com)
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.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
- "typescript"
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",