react-fathom 0.1.11 → 0.2.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 +886 -24
- package/dist/cjs/index.cjs +55 -9
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/native/index.cjs +1079 -0
- package/dist/cjs/native/index.cjs.map +1 -0
- package/dist/cjs/next/index.cjs +51 -5
- package/dist/cjs/next/index.cjs.map +1 -1
- package/dist/es/index.js +55 -9
- package/dist/es/index.js.map +1 -1
- package/dist/es/native/index.js +1071 -0
- package/dist/es/native/index.js.map +1 -0
- package/dist/es/next/index.js +51 -5
- package/dist/es/next/index.js.map +1 -1
- package/dist/react-fathom.js +55 -9
- package/dist/react-fathom.js.map +1 -1
- package/dist/react-fathom.min.js +2 -2
- package/dist/react-fathom.min.js.map +1 -1
- package/package.json +27 -4
- package/src/FathomContext.tsx +30 -1
- package/src/FathomProvider.test.tsx +115 -15
- package/src/FathomProvider.tsx +10 -2
- package/src/components/TrackClick.test.tsx +7 -7
- package/src/components/TrackClick.tsx +1 -1
- package/src/components/TrackVisible.test.tsx +7 -7
- package/src/components/TrackVisible.tsx +1 -1
- package/src/hooks/useFathom.test.tsx +14 -3
- package/src/hooks/useTrackOnClick.test.tsx +4 -4
- package/src/hooks/useTrackOnClick.ts +1 -1
- package/src/hooks/useTrackOnVisible.test.tsx +4 -4
- package/src/hooks/useTrackOnVisible.ts +1 -1
- package/src/index.ts +1 -0
- package/src/native/FathomWebView.test.tsx +410 -0
- package/src/native/FathomWebView.tsx +297 -0
- package/src/native/NativeFathomProvider.test.tsx +372 -0
- package/src/native/NativeFathomProvider.tsx +113 -0
- package/src/native/createWebViewClient.test.ts +380 -0
- package/src/native/createWebViewClient.ts +271 -0
- package/src/native/index.ts +29 -0
- package/src/native/react-native.d.ts +74 -0
- package/src/native/types.ts +145 -0
- package/src/native/useAppStateTracking.test.ts +249 -0
- package/src/native/useAppStateTracking.ts +66 -0
- package/src/native/useNavigationTracking.test.ts +446 -0
- package/src/native/useNavigationTracking.ts +177 -0
- package/src/types.ts +36 -9
- package/types/FathomContext.d.ts +1 -1
- package/types/FathomContext.d.ts.map +1 -1
- package/types/FathomProvider.d.ts.map +1 -1
- package/types/components/TrackClick.d.ts +1 -1
- package/types/components/TrackVisible.d.ts +1 -1
- package/types/hooks/useTrackOnClick.d.ts +1 -1
- package/types/hooks/useTrackOnVisible.d.ts +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/native/FathomWebView.d.ts +59 -0
- package/types/native/FathomWebView.d.ts.map +1 -0
- package/types/native/NativeFathomProvider.d.ts +36 -0
- package/types/native/NativeFathomProvider.d.ts.map +1 -0
- package/types/native/createWebViewClient.d.ts +51 -0
- package/types/native/createWebViewClient.d.ts.map +1 -0
- package/types/native/index.d.ts +10 -0
- package/types/native/index.d.ts.map +1 -0
- package/types/native/types.d.ts +125 -0
- package/types/native/types.d.ts.map +1 -0
- package/types/native/useAppStateTracking.d.ts +25 -0
- package/types/native/useAppStateTracking.d.ts.map +1 -0
- package/types/native/useNavigationTracking.d.ts +30 -0
- package/types/native/useNavigationTracking.d.ts.map +1 -0
- package/types/types.d.ts +34 -9
- package/types/types.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,36 +1,97 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-fathom
|
|
2
2
|
|
|
3
3
|
[](https://www.pkgstats.com/pkg:react-fathom)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://www.pkgstats.com/pkg:react-fathom)
|
|
6
6
|
[](https://bundlephobia.com/package/react-fathom)
|
|
7
7
|
[](https://github.com/ryanhefner/react-fathom/stargazers)
|
|
8
|
-
[](https://github.com/ryanhefner/react-fathom/network/members)
|
|
9
|
-
[](https://github.com/ryanhefner/react-fathom/issues)
|
|
10
|
-
[](https://github.com/ryanhefner/react-fathom/pulls)
|
|
11
|
-
[](https://coveralls.io/github/ryanhefner/react-fathom)
|
|
12
|
-
[](https://codecov.io/gh/ryanhefner/react-fathom)
|
|
13
|
-
[](https://circleci.com/gh/ryanhefner/react-fathom)
|
|
14
|
-
[](https://snyk.io/test/github/ryanhefner/react-fathom)
|
|
15
8
|
[](https://www.typescriptlang.org/)
|
|
16
|
-
[](https://codecov.io/gh/ryanhefner/react-fathom)
|
|
10
|
+
|
|
11
|
+
**Privacy-focused analytics for React, Next.js, and React Native.** Easily integrate [Fathom Analytics](https://usefathom.com/ref/EKONBS) into your applications with automatic pageview tracking, custom event tracking, and full TypeScript support.
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Why react-fathom?](#why-react-fathom)
|
|
17
|
+
- [Features](#features)
|
|
18
|
+
- [Installation](#install)
|
|
19
|
+
- [Usage](#usage)
|
|
20
|
+
- [Basic React Setup](#basic-react-setup)
|
|
21
|
+
- [Next.js App Router](#nextjs-app-router)
|
|
22
|
+
- [Next.js Pages Router](#nextjs-pages-router)
|
|
23
|
+
- [React Native](#react-native)
|
|
24
|
+
- [API Reference](#api)
|
|
25
|
+
- [Troubleshooting](#troubleshooting)
|
|
26
|
+
- [Contributing](#contributing)
|
|
27
|
+
- [License](#license)
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install react-fathom fathom-client
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// App.tsx or layout.tsx
|
|
37
|
+
import { FathomProvider } from 'react-fathom'
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return (
|
|
41
|
+
<FathomProvider siteId="YOUR_FATHOM_SITE_ID">
|
|
42
|
+
<YourApp />
|
|
43
|
+
</FathomProvider>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// Any component
|
|
50
|
+
import { useFathom } from 'react-fathom'
|
|
51
|
+
|
|
52
|
+
function MyComponent() {
|
|
53
|
+
const { trackEvent } = useFathom()
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<button onClick={() => trackEvent('button-click')}>
|
|
57
|
+
Click me
|
|
58
|
+
</button>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
18
62
|
|
|
19
|
-
|
|
63
|
+
That's it! Pageviews are tracked automatically.
|
|
20
64
|
|
|
21
|
-
##
|
|
65
|
+
## Why react-fathom?
|
|
22
66
|
|
|
23
|
-
|
|
67
|
+
### Privacy-First Analytics
|
|
24
68
|
|
|
25
|
-
|
|
69
|
+
[Fathom Analytics](https://usefathom.com/ref/EKONBS) is a privacy-focused alternative to Google Analytics. Unlike traditional analytics platforms:
|
|
70
|
+
|
|
71
|
+
- **No cookies required** - GDPR, CCPA, and PECR compliant out of the box
|
|
72
|
+
- **No personal data collection** - Respects user privacy by design
|
|
73
|
+
- **No consent banners needed** - Simplified compliance for your websites
|
|
74
|
+
- **Fast and lightweight** - Won't slow down your site
|
|
75
|
+
|
|
76
|
+
### Why Use This Package?
|
|
77
|
+
|
|
78
|
+
- **React-native integration** - Works seamlessly with React's component model and hooks
|
|
79
|
+
- **Automatic tracking** - Pageviews tracked automatically on route changes
|
|
80
|
+
- **Next.js optimized** - First-class support for both App Router and Pages Router
|
|
81
|
+
- **React Native support** - Full mobile support with offline queuing
|
|
82
|
+
- **TypeScript-first** - Complete type definitions for a great developer experience
|
|
83
|
+
- **Tree-shakeable** - Only bundle what you use
|
|
84
|
+
|
|
85
|
+
**New to Fathom?** Get a **$10 credit** when you sign up using [this referral link](https://usefathom.com/ref/EKONBS).
|
|
26
86
|
|
|
27
87
|
## Features
|
|
28
88
|
|
|
29
89
|
- 🚀 **Zero-config** Fathom Analytics integration for React
|
|
30
90
|
- 📦 **Tree-shakeable** - Only bundle what you use
|
|
31
91
|
- 🔄 **Automatic pageview tracking** for Next.js (Pages Router & App Router)
|
|
92
|
+
- 📱 **React Native support** with offline queuing and navigation tracking
|
|
32
93
|
- 💪 **Full TypeScript** support with type definitions
|
|
33
|
-
- 🎯 **Flexible** - Works with any React app
|
|
94
|
+
- 🎯 **Flexible** - Works with any React app, Next.js, or React Native
|
|
34
95
|
- ⚡ **Lightweight** - Minimal bundle size impact
|
|
35
96
|
|
|
36
97
|
## Install
|
|
@@ -50,9 +111,11 @@ yarn add react-fathom fathom-client
|
|
|
50
111
|
## Peer Dependencies
|
|
51
112
|
|
|
52
113
|
- `react` >= 16.8
|
|
53
|
-
- `react-dom` >= 16.8
|
|
54
|
-
- `fathom-client` >= 3.0.0
|
|
114
|
+
- `react-dom` >= 16.8 (only if using web)
|
|
115
|
+
- `fathom-client` >= 3.0.0 (only if using web, not needed for React Native)
|
|
55
116
|
- `next` >= 10.0.0 (only if using Next.js providers)
|
|
117
|
+
- `react-native` >= 0.60.0 (only if using React Native)
|
|
118
|
+
- `react-native-webview` >= 11.0.0 (only if using React Native)
|
|
56
119
|
|
|
57
120
|
## Usage
|
|
58
121
|
|
|
@@ -79,11 +142,11 @@ function MyComponent() {
|
|
|
79
142
|
const { trackPageview, trackEvent, trackGoal, load } = useFathom()
|
|
80
143
|
|
|
81
144
|
const handleClick = () => {
|
|
82
|
-
trackEvent
|
|
145
|
+
trackEvent('button-click', { _value: 100 }) // Optional: value in cents
|
|
83
146
|
}
|
|
84
147
|
|
|
85
148
|
const handlePurchase = () => {
|
|
86
|
-
trackGoal
|
|
149
|
+
trackGoal('purchase', 2999) // $29.99 in cents
|
|
87
150
|
}
|
|
88
151
|
|
|
89
152
|
return (
|
|
@@ -113,7 +176,7 @@ function MyComponent() {
|
|
|
113
176
|
// Track event on click
|
|
114
177
|
const handleClick = useTrackOnClick({
|
|
115
178
|
eventName: 'button-click',
|
|
116
|
-
|
|
179
|
+
_value: 100, // Optional: value in cents
|
|
117
180
|
callback: (e) => {
|
|
118
181
|
console.log('Tracked click!', e)
|
|
119
182
|
},
|
|
@@ -122,7 +185,7 @@ function MyComponent() {
|
|
|
122
185
|
// Track event when element becomes visible
|
|
123
186
|
const ref = useTrackOnVisible({
|
|
124
187
|
eventName: 'section-viewed',
|
|
125
|
-
|
|
188
|
+
_value: 1, // Optional: value in cents
|
|
126
189
|
callback: (entry) => {
|
|
127
190
|
console.log('Element is visible!', entry)
|
|
128
191
|
},
|
|
@@ -153,12 +216,12 @@ function MyPage() {
|
|
|
153
216
|
</TrackPageview>
|
|
154
217
|
|
|
155
218
|
{/* Track click events */}
|
|
156
|
-
<TrackClick eventName="button-click"
|
|
219
|
+
<TrackClick eventName="button-click" _value={100}>
|
|
157
220
|
<button>Sign Up</button>
|
|
158
221
|
</TrackClick>
|
|
159
222
|
|
|
160
223
|
{/* Track when element becomes visible */}
|
|
161
|
-
<TrackVisible eventName="section-viewed"
|
|
224
|
+
<TrackVisible eventName="section-viewed" _value={1}>
|
|
162
225
|
<div>Hero section</div>
|
|
163
226
|
</TrackVisible>
|
|
164
227
|
</>
|
|
@@ -231,6 +294,261 @@ function MyApp({ Component, pageProps }) {
|
|
|
231
294
|
export default MyApp
|
|
232
295
|
```
|
|
233
296
|
|
|
297
|
+
## Default Options Merging
|
|
298
|
+
|
|
299
|
+
The `FathomProvider` supports setting default options that automatically merge with any options passed to tracking calls. This is useful for setting app-wide defaults like custom event IDs or referrer information.
|
|
300
|
+
|
|
301
|
+
### How Merging Works
|
|
302
|
+
|
|
303
|
+
Default options are spread first, then any options you pass to individual tracking calls are spread second. This means:
|
|
304
|
+
|
|
305
|
+
- **Default options** provide base values for all tracking calls
|
|
306
|
+
- **Provided options** override defaults when specified
|
|
307
|
+
- You can set defaults once and forget about them
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
<FathomProvider
|
|
311
|
+
siteId="YOUR_SITE_ID"
|
|
312
|
+
defaultEventOptions={{ _site_id: 'my-app' }}
|
|
313
|
+
>
|
|
314
|
+
{/* All trackEvent calls will include _site_id: 'my-app' unless overridden */}
|
|
315
|
+
</FathomProvider>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
// Inside your component
|
|
320
|
+
const { trackEvent } = useFathom()
|
|
321
|
+
|
|
322
|
+
// Uses default: { _site_id: 'my-app' }
|
|
323
|
+
trackEvent('button-click')
|
|
324
|
+
|
|
325
|
+
// Merges with default: { _site_id: 'my-app', _value: 100 }
|
|
326
|
+
trackEvent('purchase', { _value: 100 })
|
|
327
|
+
|
|
328
|
+
// Overrides default: { _site_id: 'custom-site', _value: 50 }
|
|
329
|
+
trackEvent('special-event', { _site_id: 'custom-site', _value: 50 })
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Nested Providers
|
|
333
|
+
|
|
334
|
+
When nesting `FathomProvider` components, child providers inherit defaults from their parent but can override them:
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
<FathomProvider
|
|
338
|
+
siteId="YOUR_SITE_ID"
|
|
339
|
+
defaultEventOptions={{ _site_id: 'global' }}
|
|
340
|
+
>
|
|
341
|
+
{/* Events here use _site_id: 'global' */}
|
|
342
|
+
|
|
343
|
+
<FathomProvider defaultEventOptions={{ _site_id: 'dashboard' }}>
|
|
344
|
+
{/* Events here use _site_id: 'dashboard' */}
|
|
345
|
+
</FathomProvider>
|
|
346
|
+
</FathomProvider>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Custom Client Implementation
|
|
350
|
+
|
|
351
|
+
The `FathomProvider` accepts an optional `client` prop that allows you to provide a custom Fathom client implementation. This is useful for:
|
|
352
|
+
|
|
353
|
+
- **React Native apps** that need a custom tracking implementation
|
|
354
|
+
- **Testing** with mock clients
|
|
355
|
+
- **Server-side rendering** scenarios
|
|
356
|
+
- **Custom analytics pipelines** that wrap Fathom
|
|
357
|
+
|
|
358
|
+
### FathomClient Interface
|
|
359
|
+
|
|
360
|
+
Your custom client must implement the `FathomClient` interface:
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
import type { FathomClient, EventOptions, LoadOptions, PageViewOptions } from 'react-fathom'
|
|
364
|
+
|
|
365
|
+
const myCustomClient: FathomClient = {
|
|
366
|
+
load: (siteId: string, options?: LoadOptions) => {
|
|
367
|
+
// Initialize your tracking
|
|
368
|
+
},
|
|
369
|
+
trackPageview: (opts?: PageViewOptions) => {
|
|
370
|
+
// Track pageview
|
|
371
|
+
},
|
|
372
|
+
trackEvent: (eventName: string, opts?: EventOptions) => {
|
|
373
|
+
// Track custom event
|
|
374
|
+
},
|
|
375
|
+
trackGoal: (code: string, cents: number) => {
|
|
376
|
+
// Track goal conversion
|
|
377
|
+
},
|
|
378
|
+
setSite: (id: string) => {
|
|
379
|
+
// Change site ID
|
|
380
|
+
},
|
|
381
|
+
blockTrackingForMe: () => {
|
|
382
|
+
// Block tracking
|
|
383
|
+
},
|
|
384
|
+
enableTrackingForMe: () => {
|
|
385
|
+
// Enable tracking
|
|
386
|
+
},
|
|
387
|
+
isTrackingEnabled: () => {
|
|
388
|
+
// Return tracking status
|
|
389
|
+
return true
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### React Native
|
|
395
|
+
|
|
396
|
+
For React Native apps, use the dedicated `/native` export. This uses a hidden WebView to load Fathom's official tracking script, ensuring full compatibility with Fathom Analytics.
|
|
397
|
+
|
|
398
|
+
**Install the required peer dependency:**
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
npm install react-native-webview
|
|
402
|
+
# or
|
|
403
|
+
yarn add react-native-webview
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Basic setup:**
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
import { NativeFathomProvider } from 'react-fathom/native'
|
|
410
|
+
|
|
411
|
+
function App() {
|
|
412
|
+
return (
|
|
413
|
+
<NativeFathomProvider
|
|
414
|
+
siteId="YOUR_SITE_ID"
|
|
415
|
+
debug={__DEV__}
|
|
416
|
+
trackAppState
|
|
417
|
+
onReady={() => console.log('Fathom ready!')}
|
|
418
|
+
>
|
|
419
|
+
<YourApp />
|
|
420
|
+
</NativeFathomProvider>
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
> **Note:** The provider renders a hidden WebView (0x0 pixels) that loads the Fathom script. Events are queued until the WebView is ready, then automatically sent.
|
|
426
|
+
|
|
427
|
+
#### React Navigation Integration
|
|
428
|
+
|
|
429
|
+
Track screen navigation as pageviews with React Navigation:
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
|
|
433
|
+
import { NativeFathomProvider, useNavigationTracking } from 'react-fathom/native'
|
|
434
|
+
|
|
435
|
+
function App() {
|
|
436
|
+
const navigationRef = useNavigationContainerRef()
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<NativeFathomProvider siteId="YOUR_SITE_ID">
|
|
440
|
+
<NavigationContainer ref={navigationRef}>
|
|
441
|
+
<NavigationTracker navigationRef={navigationRef} />
|
|
442
|
+
<RootNavigator />
|
|
443
|
+
</NavigationContainer>
|
|
444
|
+
</NativeFathomProvider>
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function NavigationTracker({ navigationRef }) {
|
|
449
|
+
useNavigationTracking({
|
|
450
|
+
navigationRef,
|
|
451
|
+
transformRouteName: (name) => `/screens/${name}`,
|
|
452
|
+
})
|
|
453
|
+
return null
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### App State Tracking
|
|
458
|
+
|
|
459
|
+
Track when users foreground/background your app:
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
import { useAppStateTracking } from 'react-fathom/native'
|
|
463
|
+
|
|
464
|
+
function AppTracker() {
|
|
465
|
+
useAppStateTracking({
|
|
466
|
+
foregroundEventName: 'app-resumed',
|
|
467
|
+
backgroundEventName: 'app-paused',
|
|
468
|
+
onStateChange: (state) => console.log('App state:', state),
|
|
469
|
+
})
|
|
470
|
+
return null
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
#### Using Custom Domains
|
|
475
|
+
|
|
476
|
+
If you use [Fathom's custom domains feature](https://usefathom.com/docs/script/custom-domains), specify your domain:
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
<NativeFathomProvider
|
|
480
|
+
siteId="YOUR_SITE_ID"
|
|
481
|
+
scriptDomain="your-custom-domain.com"
|
|
482
|
+
>
|
|
483
|
+
<YourApp />
|
|
484
|
+
</NativeFathomProvider>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### Advanced: Manual WebView Client Setup
|
|
488
|
+
|
|
489
|
+
For advanced use cases, you can manually set up the WebView client:
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { useRef, useMemo, useCallback } from 'react'
|
|
493
|
+
import {
|
|
494
|
+
FathomWebView,
|
|
495
|
+
createWebViewClient,
|
|
496
|
+
FathomProvider,
|
|
497
|
+
type FathomWebViewRef,
|
|
498
|
+
} from 'react-fathom/native'
|
|
499
|
+
|
|
500
|
+
function App() {
|
|
501
|
+
const webViewRef = useRef<FathomWebViewRef>(null)
|
|
502
|
+
|
|
503
|
+
const client = useMemo(
|
|
504
|
+
() => createWebViewClient(() => webViewRef.current, { debug: __DEV__ }),
|
|
505
|
+
[]
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
const handleReady = useCallback(() => {
|
|
509
|
+
client.setWebViewReady()
|
|
510
|
+
}, [client])
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<FathomProvider client={client} siteId="YOUR_SITE_ID">
|
|
514
|
+
<FathomWebView
|
|
515
|
+
ref={webViewRef}
|
|
516
|
+
siteId="YOUR_SITE_ID"
|
|
517
|
+
onReady={handleReady}
|
|
518
|
+
/>
|
|
519
|
+
<YourApp />
|
|
520
|
+
</FathomProvider>
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Mock Client for Testing
|
|
526
|
+
|
|
527
|
+
```tsx
|
|
528
|
+
import { FathomProvider, type FathomClient } from 'react-fathom'
|
|
529
|
+
|
|
530
|
+
const mockClient: FathomClient = {
|
|
531
|
+
load: jest.fn(),
|
|
532
|
+
trackPageview: jest.fn(),
|
|
533
|
+
trackEvent: jest.fn(),
|
|
534
|
+
trackGoal: jest.fn(),
|
|
535
|
+
setSite: jest.fn(),
|
|
536
|
+
blockTrackingForMe: jest.fn(),
|
|
537
|
+
enableTrackingForMe: jest.fn(),
|
|
538
|
+
isTrackingEnabled: jest.fn(() => true),
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// In your tests
|
|
542
|
+
render(
|
|
543
|
+
<FathomProvider client={mockClient}>
|
|
544
|
+
<ComponentUnderTest />
|
|
545
|
+
</FathomProvider>
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
// Assert tracking calls
|
|
549
|
+
expect(mockClient.trackEvent).toHaveBeenCalledWith('button-click', { _value: 100 })
|
|
550
|
+
```
|
|
551
|
+
|
|
234
552
|
## API
|
|
235
553
|
|
|
236
554
|
### `FathomProvider`
|
|
@@ -241,6 +559,7 @@ Main provider component for React apps. Supports composable nesting - nested pro
|
|
|
241
559
|
|
|
242
560
|
- `siteId` (string, optional): Your Fathom Analytics site ID
|
|
243
561
|
- `client` (FathomClient, optional): Custom Fathom client instance
|
|
562
|
+
- `clientRef` (MutableRefObject<FathomClient | null>, optional): Ref that will be populated with the resolved client instance, allowing the parent component to access the client directly
|
|
244
563
|
- `clientOptions` (LoadOptions, optional): Options passed to `fathom-client`
|
|
245
564
|
- `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls
|
|
246
565
|
- `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls
|
|
@@ -251,12 +570,34 @@ Main provider component for React apps. Supports composable nesting - nested pro
|
|
|
251
570
|
<FathomProvider
|
|
252
571
|
siteId="YOUR_SITE_ID"
|
|
253
572
|
defaultPageviewOptions={{ referrer: 'https://example.com' }}
|
|
254
|
-
defaultEventOptions={{
|
|
573
|
+
defaultEventOptions={{ _site_id: 'global-site' }}
|
|
255
574
|
>
|
|
256
575
|
{/* Your app */}
|
|
257
576
|
</FathomProvider>
|
|
258
577
|
```
|
|
259
578
|
|
|
579
|
+
**Using clientRef for parent access:**
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
import { useRef } from 'react'
|
|
583
|
+
import { FathomProvider, FathomClient } from 'react-fathom'
|
|
584
|
+
|
|
585
|
+
function App() {
|
|
586
|
+
const clientRef = useRef<FathomClient>(null)
|
|
587
|
+
|
|
588
|
+
const handleDeepLink = (url: string) => {
|
|
589
|
+
// Parent can track events directly via the ref
|
|
590
|
+
clientRef.current?.trackEvent('deep_link', { _url: url })
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return (
|
|
594
|
+
<FathomProvider siteId="YOUR_SITE_ID" clientRef={clientRef}>
|
|
595
|
+
<YourApp onDeepLink={handleDeepLink} />
|
|
596
|
+
</FathomProvider>
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
260
601
|
### `NextFathomProviderApp`
|
|
261
602
|
|
|
262
603
|
Client component wrapper that combines `FathomProvider` and `NextFathomTrackViewApp` for easy integration in Next.js App Router layouts. This component is marked with `'use client'` and can be used directly in Server Components like the root `layout.tsx` file.
|
|
@@ -410,6 +751,142 @@ Component that tracks an event when it becomes visible.
|
|
|
410
751
|
- `as` (string, optional): HTML element type to render (defaults to 'div')
|
|
411
752
|
- All other `EventOptions` from `fathom-client`
|
|
412
753
|
|
|
754
|
+
## Native API
|
|
755
|
+
|
|
756
|
+
The `/native` export provides React Native-specific components and hooks. It uses a hidden WebView to load Fathom's official tracking script, ensuring full compatibility with Fathom Analytics (both Fathom Pro and self-hosted Fathom Lite).
|
|
757
|
+
|
|
758
|
+
### `NativeFathomProvider`
|
|
759
|
+
|
|
760
|
+
Convenience provider for React Native apps that manages a hidden WebView with Fathom's tracking script.
|
|
761
|
+
|
|
762
|
+
**Props:**
|
|
763
|
+
|
|
764
|
+
- `siteId` (string, required): Your Fathom Analytics site ID
|
|
765
|
+
- `loadOptions` (LoadOptions, optional): Options passed to `fathom.load()` in the WebView
|
|
766
|
+
- `scriptDomain` (string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')
|
|
767
|
+
- `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls
|
|
768
|
+
- `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls
|
|
769
|
+
- `trackAppState` (boolean, optional): Enable automatic app state tracking (defaults to false)
|
|
770
|
+
- `debug` (boolean, optional): Enable debug logging (defaults to false)
|
|
771
|
+
- `onReady` (() => void, optional): Called when the Fathom script has loaded
|
|
772
|
+
- `onError` ((error: string) => void, optional): Called when an error occurs loading the script
|
|
773
|
+
- `clientRef` (MutableRefObject<WebViewFathomClient | null>, optional): Ref that will be populated with the WebView-based client instance, allowing the parent component to access the client directly (includes queue management methods)
|
|
774
|
+
- `children` (ReactNode, required): Child components to render
|
|
775
|
+
|
|
776
|
+
**Example:**
|
|
777
|
+
|
|
778
|
+
```tsx
|
|
779
|
+
<NativeFathomProvider
|
|
780
|
+
siteId="YOUR_SITE_ID"
|
|
781
|
+
debug={__DEV__}
|
|
782
|
+
trackAppState
|
|
783
|
+
onReady={() => console.log('Analytics ready!')}
|
|
784
|
+
onError={(err) => console.error('Analytics error:', err)}
|
|
785
|
+
>
|
|
786
|
+
<App />
|
|
787
|
+
</NativeFathomProvider>
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**Using clientRef for parent access:**
|
|
791
|
+
|
|
792
|
+
```tsx
|
|
793
|
+
import { useRef } from 'react'
|
|
794
|
+
import { NativeFathomProvider, WebViewFathomClient } from 'react-fathom/native'
|
|
795
|
+
|
|
796
|
+
function App() {
|
|
797
|
+
const clientRef = useRef<WebViewFathomClient>(null)
|
|
798
|
+
|
|
799
|
+
const handleDeepLink = (url: string) => {
|
|
800
|
+
// Parent can track events directly via the ref
|
|
801
|
+
clientRef.current?.trackEvent('deep_link', { _url: url })
|
|
802
|
+
|
|
803
|
+
// Can also check queue status (React Native specific)
|
|
804
|
+
console.log('Queued events:', clientRef.current?.getQueueLength())
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return (
|
|
808
|
+
<NativeFathomProvider siteId="YOUR_SITE_ID" clientRef={clientRef}>
|
|
809
|
+
<YourApp onDeepLink={handleDeepLink} />
|
|
810
|
+
</NativeFathomProvider>
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### `FathomWebView`
|
|
816
|
+
|
|
817
|
+
Hidden WebView component that loads and manages the Fathom Analytics script. Used internally by `NativeFathomProvider`, but can be used directly for advanced setups.
|
|
818
|
+
|
|
819
|
+
**Props:**
|
|
820
|
+
|
|
821
|
+
- `siteId` (string, required): Your Fathom Analytics site ID
|
|
822
|
+
- `loadOptions` (LoadOptions, optional): Options passed to `fathom.load()`
|
|
823
|
+
- `scriptDomain` (string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')
|
|
824
|
+
- `onReady` (() => void, optional): Called when the Fathom script has loaded
|
|
825
|
+
- `onError` ((error: string) => void, optional): Called when an error occurs
|
|
826
|
+
- `debug` (boolean, optional): Enable debug logging (defaults to false)
|
|
827
|
+
|
|
828
|
+
**Ref Methods (FathomWebViewRef):**
|
|
829
|
+
|
|
830
|
+
- `trackPageview(opts?)`: Track a pageview
|
|
831
|
+
- `trackEvent(eventName, opts?)`: Track a custom event
|
|
832
|
+
- `trackGoal(code, cents)`: Track a goal conversion
|
|
833
|
+
- `blockTrackingForMe()`: Block tracking for current user
|
|
834
|
+
- `enableTrackingForMe()`: Enable tracking for current user
|
|
835
|
+
- `isReady()`: Check if the WebView is ready
|
|
836
|
+
|
|
837
|
+
### `createWebViewClient(getWebViewRef, options?)`
|
|
838
|
+
|
|
839
|
+
Factory function to create a client that communicates with a FathomWebView.
|
|
840
|
+
|
|
841
|
+
**Parameters:**
|
|
842
|
+
|
|
843
|
+
- `getWebViewRef` (() => FathomWebViewRef | null): Function that returns the WebView ref
|
|
844
|
+
- `options` (WebViewClientOptions, optional):
|
|
845
|
+
- `debug` (boolean): Enable debug logging (defaults to false)
|
|
846
|
+
- `enableQueue` (boolean): Enable command queuing before WebView is ready (defaults to true)
|
|
847
|
+
- `maxQueueSize` (number): Maximum commands to queue (defaults to 100)
|
|
848
|
+
|
|
849
|
+
**Returns:** A `FathomClient` instance with additional methods:
|
|
850
|
+
|
|
851
|
+
- `processQueue()`: Manually process queued commands (returns number of processed)
|
|
852
|
+
- `getQueueLength()`: Get the current queue length
|
|
853
|
+
- `setWebViewReady()`: Call when WebView signals it's ready (flushes queue)
|
|
854
|
+
|
|
855
|
+
### `useAppStateTracking(options?)`
|
|
856
|
+
|
|
857
|
+
Hook that tracks app state changes (foreground/background) as Fathom events.
|
|
858
|
+
|
|
859
|
+
**Options:**
|
|
860
|
+
|
|
861
|
+
- `foregroundEventName` (string, optional): Event name for foreground (defaults to 'app-foreground')
|
|
862
|
+
- `backgroundEventName` (string, optional): Event name for background (defaults to 'app-background')
|
|
863
|
+
- `eventOptions` (EventOptions, optional): Additional options for app state events
|
|
864
|
+
- `onStateChange` ((state: 'active' | 'background' | 'inactive') => void, optional): Callback on state change
|
|
865
|
+
|
|
866
|
+
### `useNavigationTracking(options)`
|
|
867
|
+
|
|
868
|
+
Hook that tracks React Navigation screen changes as pageviews.
|
|
869
|
+
|
|
870
|
+
**Options:**
|
|
871
|
+
|
|
872
|
+
- `navigationRef` (RefObject, required): React Navigation container ref
|
|
873
|
+
- `transformRouteName` ((name: string) => string, optional): Transform route names before tracking
|
|
874
|
+
- `shouldTrackRoute` ((name: string, params?: object) => boolean, optional): Filter which routes to track
|
|
875
|
+
- `includeParams` (boolean, optional): Include route params in tracked URL (defaults to false)
|
|
876
|
+
|
|
877
|
+
**Example:**
|
|
878
|
+
|
|
879
|
+
```tsx
|
|
880
|
+
const navigationRef = useNavigationContainerRef()
|
|
881
|
+
|
|
882
|
+
useNavigationTracking({
|
|
883
|
+
navigationRef,
|
|
884
|
+
transformRouteName: (name) => `/app/${name.toLowerCase()}`,
|
|
885
|
+
shouldTrackRoute: (name) => !name.startsWith('Modal'),
|
|
886
|
+
includeParams: true,
|
|
887
|
+
})
|
|
888
|
+
```
|
|
889
|
+
|
|
413
890
|
## Tree-shaking
|
|
414
891
|
|
|
415
892
|
This library is optimized for tree-shaking. When you import only what you need:
|
|
@@ -424,6 +901,391 @@ Bundlers will automatically exclude unused code, keeping your bundle size minima
|
|
|
424
901
|
|
|
425
902
|
Full TypeScript support is included. Types are automatically generated and exported.
|
|
426
903
|
|
|
904
|
+
### Exported Types
|
|
905
|
+
|
|
906
|
+
For convenience, `react-fathom` re-exports the core types from `fathom-client` so you don't need to import from multiple packages:
|
|
907
|
+
|
|
908
|
+
```tsx
|
|
909
|
+
import type {
|
|
910
|
+
// From react-fathom
|
|
911
|
+
FathomClient,
|
|
912
|
+
FathomContextInterface,
|
|
913
|
+
FathomProviderProps,
|
|
914
|
+
// Re-exported from fathom-client
|
|
915
|
+
EventOptions,
|
|
916
|
+
LoadOptions,
|
|
917
|
+
PageViewOptions,
|
|
918
|
+
} from 'react-fathom'
|
|
919
|
+
|
|
920
|
+
// No need for this anymore:
|
|
921
|
+
// import type { EventOptions } from 'fathom-client'
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
This simplifies your imports when building custom clients or working with typed event options.
|
|
925
|
+
|
|
926
|
+
## Troubleshooting
|
|
927
|
+
|
|
928
|
+
### Common Issues
|
|
929
|
+
|
|
930
|
+
#### Events not appearing in Fathom dashboard
|
|
931
|
+
|
|
932
|
+
**1. Verify your site ID**
|
|
933
|
+
|
|
934
|
+
Your site ID should match exactly what's shown in your [Fathom dashboard](https://app.usefathom.com). It's typically an 8-character alphanumeric string like `ABCD1234`.
|
|
935
|
+
|
|
936
|
+
```tsx
|
|
937
|
+
// Double-check this value
|
|
938
|
+
<FathomProvider siteId="ABCD1234">
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
**2. Check for ad blockers**
|
|
942
|
+
|
|
943
|
+
Many ad blockers and privacy extensions block analytics scripts. To test:
|
|
944
|
+
- Open an incognito/private window with extensions disabled
|
|
945
|
+
- Or temporarily whitelist your development domain
|
|
946
|
+
|
|
947
|
+
**3. Domain restrictions**
|
|
948
|
+
|
|
949
|
+
Fathom only tracks events from domains you've configured. For local development:
|
|
950
|
+
|
|
951
|
+
```tsx
|
|
952
|
+
<FathomProvider
|
|
953
|
+
siteId="YOUR_SITE_ID"
|
|
954
|
+
clientOptions={{
|
|
955
|
+
includedDomains: ['localhost', 'yourdomain.com']
|
|
956
|
+
}}
|
|
957
|
+
>
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
**4. Inspect network requests**
|
|
961
|
+
|
|
962
|
+
Open your browser's Network tab and look for requests to `cdn.usefathom.com`. If you see:
|
|
963
|
+
- **No requests**: The script isn't loading (check provider setup)
|
|
964
|
+
- **Blocked requests**: Ad blocker is interfering
|
|
965
|
+
- **Failed requests**: Check your site ID and domain configuration
|
|
966
|
+
|
|
967
|
+
#### Duplicate pageview tracking
|
|
968
|
+
|
|
969
|
+
If you're seeing double pageviews, you likely have multiple tracking setups:
|
|
970
|
+
|
|
971
|
+
```tsx
|
|
972
|
+
// WRONG: Both auto tracking AND manual tracking
|
|
973
|
+
<FathomProvider siteId="YOUR_SITE_ID">
|
|
974
|
+
<NextFathomTrackViewApp /> {/* This tracks pageviews */}
|
|
975
|
+
{/* AND clientOptions.auto defaults to true, which also tracks */}
|
|
976
|
+
</FathomProvider>
|
|
977
|
+
|
|
978
|
+
// CORRECT: Use one or the other
|
|
979
|
+
<FathomProvider siteId="YOUR_SITE_ID" clientOptions={{ auto: false }}>
|
|
980
|
+
<NextFathomTrackViewApp />
|
|
981
|
+
</FathomProvider>
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
#### useFathom returns undefined methods
|
|
985
|
+
|
|
986
|
+
This was the old behavior. As of the latest version, `useFathom()` returns stub methods that warn in development when called outside a provider. If you're seeing `undefined`:
|
|
987
|
+
|
|
988
|
+
1. Update to the latest version: `npm update react-fathom`
|
|
989
|
+
2. Ensure your component is inside a `FathomProvider`
|
|
990
|
+
|
|
991
|
+
#### Next.js App Router: "use client" errors
|
|
992
|
+
|
|
993
|
+
Server Components can't use hooks directly. Use the pre-configured client component:
|
|
994
|
+
|
|
995
|
+
```tsx
|
|
996
|
+
// app/layout.tsx
|
|
997
|
+
import { NextFathomProviderApp } from 'react-fathom/next'
|
|
998
|
+
|
|
999
|
+
export default function RootLayout({ children }) {
|
|
1000
|
+
return (
|
|
1001
|
+
<html>
|
|
1002
|
+
<body>
|
|
1003
|
+
<NextFathomProviderApp siteId="YOUR_SITE_ID">
|
|
1004
|
+
{children}
|
|
1005
|
+
</NextFathomProviderApp>
|
|
1006
|
+
</body>
|
|
1007
|
+
</html>
|
|
1008
|
+
)
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
If you need a custom setup, create your own client component wrapper:
|
|
1013
|
+
|
|
1014
|
+
```tsx
|
|
1015
|
+
// components/AnalyticsProvider.tsx
|
|
1016
|
+
'use client'
|
|
1017
|
+
import { FathomProvider } from 'react-fathom'
|
|
1018
|
+
|
|
1019
|
+
export function AnalyticsProvider({ children }) {
|
|
1020
|
+
return (
|
|
1021
|
+
<FathomProvider siteId={process.env.NEXT_PUBLIC_FATHOM_SITE_ID}>
|
|
1022
|
+
{children}
|
|
1023
|
+
</FathomProvider>
|
|
1024
|
+
)
|
|
1025
|
+
}
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
#### Next.js: Environment variables not loading
|
|
1029
|
+
|
|
1030
|
+
Ensure your environment variable is prefixed with `NEXT_PUBLIC_` to be available client-side:
|
|
1031
|
+
|
|
1032
|
+
```bash
|
|
1033
|
+
# .env.local
|
|
1034
|
+
NEXT_PUBLIC_FATHOM_SITE_ID=YOUR_SITE_ID # ✓ Correct
|
|
1035
|
+
FATHOM_SITE_ID=YOUR_SITE_ID # ✗ Won't work client-side
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
#### React Native: Events not sending
|
|
1039
|
+
|
|
1040
|
+
**1. Verify react-native-webview is installed**
|
|
1041
|
+
|
|
1042
|
+
The native module requires `react-native-webview`:
|
|
1043
|
+
|
|
1044
|
+
```bash
|
|
1045
|
+
npm install react-native-webview
|
|
1046
|
+
# For iOS, also run:
|
|
1047
|
+
cd ios && pod install
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
**2. Check WebView is ready**
|
|
1051
|
+
|
|
1052
|
+
Events are queued until the WebView loads. Use the `onReady` callback to verify:
|
|
1053
|
+
|
|
1054
|
+
```tsx
|
|
1055
|
+
<NativeFathomProvider
|
|
1056
|
+
siteId="YOUR_SITE_ID"
|
|
1057
|
+
debug={true}
|
|
1058
|
+
onReady={() => console.log('Fathom WebView ready!')}
|
|
1059
|
+
onError={(err) => console.error('Fathom error:', err)}
|
|
1060
|
+
>
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
**3. Verify network connectivity**
|
|
1064
|
+
|
|
1065
|
+
The WebView needs network access to load the Fathom script. Events are queued before the WebView is ready but won't send if the script fails to load.
|
|
1066
|
+
|
|
1067
|
+
**4. Check for WebView restrictions**
|
|
1068
|
+
|
|
1069
|
+
Some enterprise MDM solutions or app configurations may block WebViews from loading external scripts. Verify that `cdn.usefathom.com` (or your custom domain) is accessible.
|
|
1070
|
+
|
|
1071
|
+
**5. Debug with logging**
|
|
1072
|
+
|
|
1073
|
+
Enable debug mode to see all tracking activity:
|
|
1074
|
+
|
|
1075
|
+
```tsx
|
|
1076
|
+
<NativeFathomProvider
|
|
1077
|
+
siteId="YOUR_SITE_ID"
|
|
1078
|
+
debug={__DEV__} // Logs all tracking calls
|
|
1079
|
+
>
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
### Debugging Tips
|
|
1083
|
+
|
|
1084
|
+
#### Enable verbose logging
|
|
1085
|
+
|
|
1086
|
+
For web, check the browser console. For React Native, enable debug mode:
|
|
1087
|
+
|
|
1088
|
+
```tsx
|
|
1089
|
+
// React Native
|
|
1090
|
+
<NativeFathomProvider
|
|
1091
|
+
siteId="YOUR_SITE_ID"
|
|
1092
|
+
debug={__DEV__}
|
|
1093
|
+
>
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
#### Verify tracking in real-time
|
|
1097
|
+
|
|
1098
|
+
Fathom's dashboard updates in real-time. Open your dashboard alongside your app to see events as they're tracked.
|
|
1099
|
+
|
|
1100
|
+
#### Test with a mock client
|
|
1101
|
+
|
|
1102
|
+
For debugging, replace the real client with a mock that logs everything:
|
|
1103
|
+
|
|
1104
|
+
```tsx
|
|
1105
|
+
const debugClient = {
|
|
1106
|
+
load: (id, opts) => console.log('load:', id, opts),
|
|
1107
|
+
trackPageview: (opts) => console.log('pageview:', opts),
|
|
1108
|
+
trackEvent: (name, opts) => console.log('event:', name, opts),
|
|
1109
|
+
trackGoal: (code, cents) => console.log('goal:', code, cents),
|
|
1110
|
+
setSite: (id) => console.log('setSite:', id),
|
|
1111
|
+
blockTrackingForMe: () => console.log('blocked'),
|
|
1112
|
+
enableTrackingForMe: () => console.log('enabled'),
|
|
1113
|
+
isTrackingEnabled: () => true,
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
<FathomProvider client={debugClient}>
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
### Getting Help
|
|
1120
|
+
|
|
1121
|
+
- [Open an issue](https://github.com/ryanhefner/react-fathom/issues) on GitHub
|
|
1122
|
+
- [Search existing issues](https://github.com/ryanhefner/react-fathom/issues?q=is%3Aissue) for solutions
|
|
1123
|
+
- [Fathom Analytics documentation](https://usefathom.com/docs) for platform-specific questions
|
|
1124
|
+
|
|
1125
|
+
## Contributing
|
|
1126
|
+
|
|
1127
|
+
Contributions are welcome! Whether it's bug fixes, new features, documentation improvements, or examples, we appreciate your help.
|
|
1128
|
+
|
|
1129
|
+
### Ways to Contribute
|
|
1130
|
+
|
|
1131
|
+
| Type | Description |
|
|
1132
|
+
|------|-------------|
|
|
1133
|
+
| **Bug Reports** | Found a bug? [Open an issue](https://github.com/ryanhefner/react-fathom/issues/new) with reproduction steps |
|
|
1134
|
+
| **Feature Requests** | Have an idea? Discuss it in an issue first |
|
|
1135
|
+
| **Bug Fixes** | PRs for documented issues are always welcome |
|
|
1136
|
+
| **Documentation** | Help improve docs, add examples, fix typos |
|
|
1137
|
+
| **Tests** | Increase test coverage or add edge case tests |
|
|
1138
|
+
|
|
1139
|
+
### Development Setup
|
|
1140
|
+
|
|
1141
|
+
**Prerequisites:**
|
|
1142
|
+
- Node.js 18+
|
|
1143
|
+
- npm 9+
|
|
1144
|
+
|
|
1145
|
+
**1. Clone and install:**
|
|
1146
|
+
|
|
1147
|
+
```bash
|
|
1148
|
+
git clone https://github.com/ryanhefner/react-fathom.git
|
|
1149
|
+
cd react-fathom
|
|
1150
|
+
npm install
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
**2. Run the development workflow:**
|
|
1154
|
+
|
|
1155
|
+
```bash
|
|
1156
|
+
# Run tests in watch mode during development
|
|
1157
|
+
npm run test:watch
|
|
1158
|
+
|
|
1159
|
+
# Run the full test suite
|
|
1160
|
+
npm test
|
|
1161
|
+
|
|
1162
|
+
# Build the package
|
|
1163
|
+
npm run build
|
|
1164
|
+
|
|
1165
|
+
# Type check without emitting
|
|
1166
|
+
npm run typecheck
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
### Project Structure
|
|
1170
|
+
|
|
1171
|
+
```
|
|
1172
|
+
react-fathom/
|
|
1173
|
+
├── src/
|
|
1174
|
+
│ ├── index.ts # Main entry point
|
|
1175
|
+
│ ├── FathomProvider.tsx # Core provider component
|
|
1176
|
+
│ ├── FathomContext.tsx # React context
|
|
1177
|
+
│ ├── types.ts # TypeScript definitions
|
|
1178
|
+
│ ├── hooks/ # React hooks
|
|
1179
|
+
│ │ ├── useFathom.ts
|
|
1180
|
+
│ │ ├── useTrackOnClick.ts
|
|
1181
|
+
│ │ ├── useTrackOnMount.ts
|
|
1182
|
+
│ │ └── useTrackOnVisible.ts
|
|
1183
|
+
│ ├── components/ # Declarative tracking components
|
|
1184
|
+
│ │ ├── TrackClick.tsx
|
|
1185
|
+
│ │ ├── TrackPageview.tsx
|
|
1186
|
+
│ │ └── TrackVisible.tsx
|
|
1187
|
+
│ ├── next/ # Next.js-specific exports
|
|
1188
|
+
│ │ └── index.ts
|
|
1189
|
+
│ └── native/ # React Native exports
|
|
1190
|
+
│ ├── index.ts
|
|
1191
|
+
│ ├── FathomWebView.tsx
|
|
1192
|
+
│ ├── createWebViewClient.ts
|
|
1193
|
+
│ ├── NativeFathomProvider.tsx
|
|
1194
|
+
│ ├── useNavigationTracking.ts
|
|
1195
|
+
│ └── useAppStateTracking.ts
|
|
1196
|
+
├── examples/ # Example applications
|
|
1197
|
+
│ ├── next-app/ # Next.js App Router example
|
|
1198
|
+
│ └── next-pages/ # Next.js Pages Router example
|
|
1199
|
+
└── dist/ # Built output (generated)
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
### Testing Guidelines
|
|
1203
|
+
|
|
1204
|
+
We use [Vitest](https://vitest.dev/) for testing. All new features should include tests.
|
|
1205
|
+
|
|
1206
|
+
```bash
|
|
1207
|
+
# Run all tests
|
|
1208
|
+
npm test
|
|
1209
|
+
|
|
1210
|
+
# Run tests in watch mode
|
|
1211
|
+
npm run test:watch
|
|
1212
|
+
|
|
1213
|
+
# Run tests with coverage
|
|
1214
|
+
npm run test:coverage
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
**Writing tests:**
|
|
1218
|
+
|
|
1219
|
+
```tsx
|
|
1220
|
+
// src/hooks/useMyHook.test.tsx
|
|
1221
|
+
import { renderHook } from '@testing-library/react'
|
|
1222
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
1223
|
+
import { useMyHook } from './useMyHook'
|
|
1224
|
+
import { FathomProvider } from '../FathomProvider'
|
|
1225
|
+
|
|
1226
|
+
describe('useMyHook', () => {
|
|
1227
|
+
it('should track events correctly', () => {
|
|
1228
|
+
const mockClient = {
|
|
1229
|
+
trackEvent: vi.fn(),
|
|
1230
|
+
// ... other required methods
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const wrapper = ({ children }) => (
|
|
1234
|
+
<FathomProvider client={mockClient}>{children}</FathomProvider>
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
const { result } = renderHook(() => useMyHook(), { wrapper })
|
|
1238
|
+
|
|
1239
|
+
result.current.doSomething()
|
|
1240
|
+
|
|
1241
|
+
expect(mockClient.trackEvent).toHaveBeenCalledWith('expected-event', {})
|
|
1242
|
+
})
|
|
1243
|
+
})
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
### Code Style
|
|
1247
|
+
|
|
1248
|
+
- **TypeScript**: All code should be fully typed
|
|
1249
|
+
- **Formatting**: We use Prettier (run `npm run format` before committing)
|
|
1250
|
+
- **Linting**: ESLint catches common issues (run `npm run lint`)
|
|
1251
|
+
- **Naming**:
|
|
1252
|
+
- Components: PascalCase (`TrackClick.tsx`)
|
|
1253
|
+
- Hooks: camelCase with `use` prefix (`useFathom.ts`)
|
|
1254
|
+
- Types: PascalCase (`FathomClient`)
|
|
1255
|
+
|
|
1256
|
+
### Submitting a Pull Request
|
|
1257
|
+
|
|
1258
|
+
1. **Fork** the repository
|
|
1259
|
+
2. **Create a branch** from `main`:
|
|
1260
|
+
```bash
|
|
1261
|
+
git checkout -b fix/my-bug-fix
|
|
1262
|
+
# or
|
|
1263
|
+
git checkout -b feature/my-new-feature
|
|
1264
|
+
```
|
|
1265
|
+
3. **Make your changes** with clear, focused commits
|
|
1266
|
+
4. **Add or update tests** for your changes
|
|
1267
|
+
5. **Ensure CI passes**:
|
|
1268
|
+
```bash
|
|
1269
|
+
npm run lint
|
|
1270
|
+
npm test
|
|
1271
|
+
npm run build
|
|
1272
|
+
```
|
|
1273
|
+
6. **Submit a PR** with a clear description of what and why
|
|
1274
|
+
|
|
1275
|
+
### Commit Message Guidelines
|
|
1276
|
+
|
|
1277
|
+
Use clear, descriptive commit messages:
|
|
1278
|
+
|
|
1279
|
+
```
|
|
1280
|
+
feat: add useTrackOnScroll hook for scroll tracking
|
|
1281
|
+
fix: resolve duplicate pageview tracking in Next.js
|
|
1282
|
+
docs: add troubleshooting section for ad blockers
|
|
1283
|
+
test: add tests for native offline queue
|
|
1284
|
+
refactor: simplify FathomContext default values
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
Prefixes: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `perf`
|
|
1288
|
+
|
|
427
1289
|
## License
|
|
428
1290
|
|
|
429
1291
|
[MIT](LICENSE) © [Ryan Hefner](https://www.ryanhefner.com)
|