@usefy/use-geolocation 0.0.28
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 +645 -0
- package/dist/index.d.mts +283 -0
- package/dist/index.d.ts +283 -0
- package/dist/index.js +300 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +271 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/mirunamu00/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-geolocation</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A powerful React hook for accessing device geolocation with real-time tracking and distance calculation</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-geolocation">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-geolocation.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-geolocation">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-geolocation.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-geolocation">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-geolocation?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/mirunamu00/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-geolocation.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://mirunamu00.github.io/usefy/?path=/docs/hooks-usegeolocation--docs" target="_blank" rel="noopener noreferrer">
|
|
36
|
+
<strong>📚 View Storybook Demo</strong>
|
|
37
|
+
</a>
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
`@usefy/use-geolocation` is a feature-rich React hook for accessing device geolocation with real-time tracking, distance calculation, and comprehensive error handling. It provides a simple API for getting current position, watching position changes, calculating distances, and tracking permission states.
|
|
45
|
+
|
|
46
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
47
|
+
|
|
48
|
+
### Why use-geolocation?
|
|
49
|
+
|
|
50
|
+
- **Zero Dependencies** — Pure React implementation with no external dependencies
|
|
51
|
+
- **TypeScript First** — Full type safety with comprehensive type definitions
|
|
52
|
+
- **Real-Time Tracking** — Watch position changes as device moves
|
|
53
|
+
- **Distance Calculation** — Built-in Haversine formula for accurate distance calculations
|
|
54
|
+
- **Bearing Calculation** — Calculate direction/bearing between coordinates
|
|
55
|
+
- **Permission Tracking** — Monitor geolocation permission state changes
|
|
56
|
+
- **Error Handling** — Comprehensive error handling with typed error codes
|
|
57
|
+
- **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
58
|
+
- **Stable References** — Memoized functions for optimal performance
|
|
59
|
+
- **Well Tested** — Comprehensive test coverage with Vitest
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# npm
|
|
67
|
+
npm install @usefy/use-geolocation
|
|
68
|
+
|
|
69
|
+
# yarn
|
|
70
|
+
yarn add @usefy/use-geolocation
|
|
71
|
+
|
|
72
|
+
# pnpm
|
|
73
|
+
pnpm add @usefy/use-geolocation
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Peer Dependencies
|
|
77
|
+
|
|
78
|
+
This package requires React 18 or 19:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
94
|
+
|
|
95
|
+
function MyLocation() {
|
|
96
|
+
const { position, loading, error } = useGeolocation();
|
|
97
|
+
|
|
98
|
+
if (loading) return <p>Loading location...</p>;
|
|
99
|
+
if (error) return <p>Error: {error.message}</p>;
|
|
100
|
+
if (!position) return <p>No position yet</p>;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<p>Latitude: {position.coords.latitude}</p>
|
|
105
|
+
<p>Longitude: {position.coords.longitude}</p>
|
|
106
|
+
<p>Accuracy: {position.coords.accuracy}m</p>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## API Reference
|
|
115
|
+
|
|
116
|
+
### `useGeolocation(options?)`
|
|
117
|
+
|
|
118
|
+
A hook that manages geolocation state with real-time tracking and utility functions.
|
|
119
|
+
|
|
120
|
+
#### Parameters
|
|
121
|
+
|
|
122
|
+
| Parameter | Type | Description |
|
|
123
|
+
| --------- | ------------------------- | ----------------------------- |
|
|
124
|
+
| `options` | `UseGeolocationOptions` | Optional configuration object |
|
|
125
|
+
|
|
126
|
+
#### Options
|
|
127
|
+
|
|
128
|
+
| Option | Type | Default | Description |
|
|
129
|
+
| ------------------- | --------------------------------------- | --------- | ----------------------------------------------------------------- |
|
|
130
|
+
| `enableHighAccuracy` | `boolean` | `false` | Enable high accuracy mode (uses GPS, consumes more battery) |
|
|
131
|
+
| `maximumAge` | `number` | `0` | Maximum age of cached position in milliseconds |
|
|
132
|
+
| `timeout` | `number` | `30000` | Timeout in milliseconds for position request |
|
|
133
|
+
| `watch` | `boolean` | `false` | Start watching position immediately on mount |
|
|
134
|
+
| `immediate` | `boolean` | `true` | Get initial position immediately on mount |
|
|
135
|
+
| `onSuccess` | `(position: GeoPosition) => void` | — | Callback when position is successfully retrieved |
|
|
136
|
+
| `onError` | `(error: GeolocationError) => void` | — | Callback when an error occurs |
|
|
137
|
+
| `onPositionChange` | `(position: GeoPosition) => void` | — | Callback when position changes (during watch mode) |
|
|
138
|
+
| `onPermissionChange` | `(state: PermissionState) => void` | — | Callback when permission state changes |
|
|
139
|
+
|
|
140
|
+
#### Returns `UseGeolocationReturn`
|
|
141
|
+
|
|
142
|
+
| Property | Type | Description |
|
|
143
|
+
| ------------------- | --------------------------------------- | ---------------------------------------------------------------- |
|
|
144
|
+
| `position` | `GeoPosition \| null` | Current position data (null if not yet retrieved) |
|
|
145
|
+
| `loading` | `boolean` | Loading state (true while fetching position) |
|
|
146
|
+
| `error` | `GeolocationError \| null` | Error object (null if no error) |
|
|
147
|
+
| `permission` | `PermissionState` | Current permission state (`prompt`, `granted`, `denied`, `unavailable`) |
|
|
148
|
+
| `isSupported` | `boolean` | Whether geolocation is supported in this environment |
|
|
149
|
+
| `getCurrentPosition` | `() => void` | Manually get current position (one-time request) |
|
|
150
|
+
| `watchPosition` | `() => void` | Start watching position for real-time updates |
|
|
151
|
+
| `clearWatch` | `() => void` | Stop watching position |
|
|
152
|
+
| `distanceFrom` | `(lat: number, lon: number) => number \| null` | Calculate distance from current position to target coordinates in meters |
|
|
153
|
+
| `bearingTo` | `(lat: number, lon: number) => number \| null` | Calculate bearing/direction from current position to target coordinates (0-360 degrees) |
|
|
154
|
+
|
|
155
|
+
#### Error Codes
|
|
156
|
+
|
|
157
|
+
| Code | Description |
|
|
158
|
+
| --------------------- | ---------------------------------------------- |
|
|
159
|
+
| `PERMISSION_DENIED` | User denied geolocation permission |
|
|
160
|
+
| `POSITION_UNAVAILABLE` | Position information unavailable |
|
|
161
|
+
| `TIMEOUT` | Position request timed out |
|
|
162
|
+
| `NOT_SUPPORTED` | Geolocation is not supported in this environment |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Examples
|
|
167
|
+
|
|
168
|
+
### Basic Usage
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
172
|
+
|
|
173
|
+
function CurrentLocation() {
|
|
174
|
+
const { position, loading, error } = useGeolocation();
|
|
175
|
+
|
|
176
|
+
if (loading) return <div>Loading location...</div>;
|
|
177
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
178
|
+
if (!position) return <div>No position available</div>;
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div>
|
|
182
|
+
<p>Latitude: {position.coords.latitude}</p>
|
|
183
|
+
<p>Longitude: {position.coords.longitude}</p>
|
|
184
|
+
<p>Accuracy: {position.coords.accuracy}m</p>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Manual Control
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
194
|
+
|
|
195
|
+
function ManualLocation() {
|
|
196
|
+
const {
|
|
197
|
+
position,
|
|
198
|
+
loading,
|
|
199
|
+
error,
|
|
200
|
+
getCurrentPosition,
|
|
201
|
+
watchPosition,
|
|
202
|
+
clearWatch,
|
|
203
|
+
} = useGeolocation({ immediate: false, watch: false });
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div>
|
|
207
|
+
<button onClick={getCurrentPosition} disabled={loading}>
|
|
208
|
+
Get Location
|
|
209
|
+
</button>
|
|
210
|
+
<button onClick={watchPosition}>Start Tracking</button>
|
|
211
|
+
<button onClick={clearWatch}>Stop Tracking</button>
|
|
212
|
+
|
|
213
|
+
{position && (
|
|
214
|
+
<p>
|
|
215
|
+
{position.coords.latitude}, {position.coords.longitude}
|
|
216
|
+
</p>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Real-Time Tracking
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
227
|
+
|
|
228
|
+
function LiveTracking() {
|
|
229
|
+
const { position, watchPosition, clearWatch } = useGeolocation({
|
|
230
|
+
immediate: false,
|
|
231
|
+
watch: false,
|
|
232
|
+
onPositionChange: (pos) => {
|
|
233
|
+
console.log("Position updated:", pos);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const [isTracking, setIsTracking] = useState(false);
|
|
238
|
+
|
|
239
|
+
const handleStart = () => {
|
|
240
|
+
watchPosition();
|
|
241
|
+
setIsTracking(true);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const handleStop = () => {
|
|
245
|
+
clearWatch();
|
|
246
|
+
setIsTracking(false);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div>
|
|
251
|
+
<button onClick={handleStart} disabled={isTracking}>
|
|
252
|
+
Start Tracking
|
|
253
|
+
</button>
|
|
254
|
+
<button onClick={handleStop} disabled={!isTracking}>
|
|
255
|
+
Stop Tracking
|
|
256
|
+
</button>
|
|
257
|
+
|
|
258
|
+
{isTracking && <p>🔴 Live tracking active</p>}
|
|
259
|
+
|
|
260
|
+
{position && (
|
|
261
|
+
<p>
|
|
262
|
+
{position.coords.latitude.toFixed(6)},{" "}
|
|
263
|
+
{position.coords.longitude.toFixed(6)}
|
|
264
|
+
</p>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Distance Calculation
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
275
|
+
|
|
276
|
+
function DistanceToDestination() {
|
|
277
|
+
const { position, distanceFrom } = useGeolocation();
|
|
278
|
+
|
|
279
|
+
// New York City coordinates
|
|
280
|
+
const nyLat = 40.7128;
|
|
281
|
+
const nyLon = -74.0060;
|
|
282
|
+
|
|
283
|
+
const distance = distanceFrom(nyLat, nyLon);
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div>
|
|
287
|
+
{distance && (
|
|
288
|
+
<p>Distance to NYC: {(distance / 1000).toFixed(2)} km</p>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Bearing/Direction Calculation
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
299
|
+
|
|
300
|
+
function DirectionToDestination() {
|
|
301
|
+
const { position, bearingTo } = useGeolocation();
|
|
302
|
+
|
|
303
|
+
// London coordinates
|
|
304
|
+
const londonLat = 51.5074;
|
|
305
|
+
const londonLon = -0.1278;
|
|
306
|
+
|
|
307
|
+
const bearing = bearingTo(londonLat, londonLon);
|
|
308
|
+
|
|
309
|
+
const getDirection = (bearing: number | null) => {
|
|
310
|
+
if (bearing === null) return "—";
|
|
311
|
+
if (bearing < 22.5 || bearing >= 337.5) return "North";
|
|
312
|
+
if (bearing < 67.5) return "Northeast";
|
|
313
|
+
if (bearing < 112.5) return "East";
|
|
314
|
+
if (bearing < 157.5) return "Southeast";
|
|
315
|
+
if (bearing < 202.5) return "South";
|
|
316
|
+
if (bearing < 247.5) return "Southwest";
|
|
317
|
+
if (bearing < 292.5) return "West";
|
|
318
|
+
return "Northwest";
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<div>
|
|
323
|
+
{bearing !== null && (
|
|
324
|
+
<p>
|
|
325
|
+
Direction to London: {getDirection(bearing)} ({bearing.toFixed(0)}°)
|
|
326
|
+
</p>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### High Accuracy Mode
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
337
|
+
|
|
338
|
+
function HighAccuracyLocation() {
|
|
339
|
+
const { position, loading } = useGeolocation({
|
|
340
|
+
enableHighAccuracy: true,
|
|
341
|
+
timeout: 10000,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (loading) return <div>Getting high accuracy location...</div>;
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<div>
|
|
348
|
+
{position && (
|
|
349
|
+
<p>
|
|
350
|
+
High accuracy: {position.coords.accuracy.toFixed(1)}m
|
|
351
|
+
</p>
|
|
352
|
+
)}
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Permission State Tracking
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
362
|
+
|
|
363
|
+
function PermissionStatus() {
|
|
364
|
+
const { permission, onPermissionChange } = useGeolocation({
|
|
365
|
+
onPermissionChange: (state) => {
|
|
366
|
+
console.log("Permission changed:", state);
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<div>
|
|
372
|
+
<p>Permission: {permission}</p>
|
|
373
|
+
{permission === "denied" && (
|
|
374
|
+
<p>Please enable location permissions in your browser settings.</p>
|
|
375
|
+
)}
|
|
376
|
+
</div>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Error Handling
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
385
|
+
|
|
386
|
+
function RobustLocation() {
|
|
387
|
+
const { position, error, getCurrentPosition } = useGeolocation({
|
|
388
|
+
immediate: false,
|
|
389
|
+
onError: (err) => {
|
|
390
|
+
console.error("Geolocation error:", err.code, err.message);
|
|
391
|
+
|
|
392
|
+
if (err.code === "PERMISSION_DENIED") {
|
|
393
|
+
alert("Please allow location access");
|
|
394
|
+
} else if (err.code === "TIMEOUT") {
|
|
395
|
+
alert("Location request timed out. Please try again.");
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<div>
|
|
402
|
+
<button onClick={getCurrentPosition}>Get Location</button>
|
|
403
|
+
|
|
404
|
+
{error && (
|
|
405
|
+
<div className="error">
|
|
406
|
+
<p>Error: {error.code}</p>
|
|
407
|
+
<p>{error.message}</p>
|
|
408
|
+
</div>
|
|
409
|
+
)}
|
|
410
|
+
|
|
411
|
+
{position && (
|
|
412
|
+
<p>
|
|
413
|
+
{position.coords.latitude}, {position.coords.longitude}
|
|
414
|
+
</p>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Auto-Watch Mode
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
import { useGeolocation } from "@usefy/use-geolocation";
|
|
425
|
+
|
|
426
|
+
function AutoTracking() {
|
|
427
|
+
const { position, clearWatch } = useGeolocation({
|
|
428
|
+
watch: true, // Automatically start watching on mount
|
|
429
|
+
immediate: false,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<div>
|
|
434
|
+
<p>Auto-tracking is active</p>
|
|
435
|
+
<button onClick={clearWatch}>Stop</button>
|
|
436
|
+
|
|
437
|
+
{position && (
|
|
438
|
+
<p>
|
|
439
|
+
{position.coords.latitude.toFixed(6)},{" "}
|
|
440
|
+
{position.coords.longitude.toFixed(6)}
|
|
441
|
+
</p>
|
|
442
|
+
)}
|
|
443
|
+
</div>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## TypeScript
|
|
451
|
+
|
|
452
|
+
This hook is written in TypeScript and exports comprehensive type definitions.
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
import {
|
|
456
|
+
useGeolocation,
|
|
457
|
+
type UseGeolocationOptions,
|
|
458
|
+
type UseGeolocationReturn,
|
|
459
|
+
type GeoPosition,
|
|
460
|
+
type GeolocationError,
|
|
461
|
+
type PermissionState,
|
|
462
|
+
} from "@usefy/use-geolocation";
|
|
463
|
+
|
|
464
|
+
// Full type inference
|
|
465
|
+
const geolocation: UseGeolocationReturn = useGeolocation({
|
|
466
|
+
enableHighAccuracy: true,
|
|
467
|
+
timeout: 10000,
|
|
468
|
+
onSuccess: (position: GeoPosition) => {
|
|
469
|
+
console.log("Got position:", position);
|
|
470
|
+
},
|
|
471
|
+
onError: (error: GeolocationError) => {
|
|
472
|
+
console.error("Error:", error.code, error.message);
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Permission state is typed as union
|
|
477
|
+
const permission: PermissionState = geolocation.permission;
|
|
478
|
+
// "prompt" | "granted" | "denied" | "unavailable"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Performance
|
|
484
|
+
|
|
485
|
+
- **Stable Function References** — All control functions are memoized with `useCallback`
|
|
486
|
+
- **Smart Re-renders** — Only re-renders when position, loading, or error state changes
|
|
487
|
+
- **Automatic Cleanup** — Watch is automatically cleared on unmount
|
|
488
|
+
- **Options Auto-Restart** — Watch automatically restarts when options change
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
const { getCurrentPosition, watchPosition, clearWatch } = useGeolocation();
|
|
492
|
+
|
|
493
|
+
// These references remain stable across renders
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
// Safe to use as dependencies
|
|
496
|
+
}, [getCurrentPosition, watchPosition, clearWatch]);
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Testing
|
|
502
|
+
|
|
503
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
504
|
+
|
|
505
|
+
### Test Coverage
|
|
506
|
+
|
|
507
|
+
📊 <a href="https://mirunamu00.github.io/usefy/coverage/use-geolocation/src/index.html" target="_blank" rel="noopener noreferrer"><strong>View Detailed Coverage Report</strong></a> (GitHub Pages)
|
|
508
|
+
|
|
509
|
+
### Test Categories
|
|
510
|
+
|
|
511
|
+
<details>
|
|
512
|
+
<summary><strong>Initialization Tests</strong></summary>
|
|
513
|
+
|
|
514
|
+
- Initialize with correct default state
|
|
515
|
+
- Detect unsupported environment
|
|
516
|
+
- Set loading state when immediate is true
|
|
517
|
+
|
|
518
|
+
</details>
|
|
519
|
+
|
|
520
|
+
<details>
|
|
521
|
+
<summary><strong>getCurrentPosition Tests</strong></summary>
|
|
522
|
+
|
|
523
|
+
- Get current position successfully
|
|
524
|
+
- Handle PERMISSION_DENIED error
|
|
525
|
+
- Handle POSITION_UNAVAILABLE error
|
|
526
|
+
- Handle TIMEOUT error
|
|
527
|
+
- Handle NOT_SUPPORTED error when geolocation unavailable
|
|
528
|
+
|
|
529
|
+
</details>
|
|
530
|
+
|
|
531
|
+
<details>
|
|
532
|
+
<summary><strong>watchPosition Tests</strong></summary>
|
|
533
|
+
|
|
534
|
+
- Watch position and receive updates
|
|
535
|
+
- Clear watch on clearWatch call
|
|
536
|
+
- Clear existing watch before starting new one
|
|
537
|
+
- Call onPositionChange callback on updates
|
|
538
|
+
|
|
539
|
+
</details>
|
|
540
|
+
|
|
541
|
+
<details>
|
|
542
|
+
<summary><strong>Permission State Tests</strong></summary>
|
|
543
|
+
|
|
544
|
+
- Track permission state changes
|
|
545
|
+
- Set permission to unavailable when permissions API fails
|
|
546
|
+
- Call onPermissionChange callback when permission changes
|
|
547
|
+
|
|
548
|
+
</details>
|
|
549
|
+
|
|
550
|
+
<details>
|
|
551
|
+
<summary><strong>Utility Functions Tests</strong></summary>
|
|
552
|
+
|
|
553
|
+
- Calculate distance correctly using Haversine formula
|
|
554
|
+
- Return null when no position available for distanceFrom
|
|
555
|
+
- Calculate bearing correctly
|
|
556
|
+
- Return null when no position available for bearingTo
|
|
557
|
+
|
|
558
|
+
</details>
|
|
559
|
+
|
|
560
|
+
<details>
|
|
561
|
+
<summary><strong>Options Tests</strong></summary>
|
|
562
|
+
|
|
563
|
+
- Use custom options (enableHighAccuracy, timeout, maximumAge)
|
|
564
|
+
- Use default timeout of 30000ms
|
|
565
|
+
- Call onSuccess callback
|
|
566
|
+
- Call onError callback
|
|
567
|
+
|
|
568
|
+
</details>
|
|
569
|
+
|
|
570
|
+
<details>
|
|
571
|
+
<summary><strong>Auto-Watch and Immediate Tests</strong></summary>
|
|
572
|
+
|
|
573
|
+
- Start watching immediately when watch: true
|
|
574
|
+
- Get position immediately when immediate: true
|
|
575
|
+
- Not fetch position when immediate: false
|
|
576
|
+
|
|
577
|
+
</details>
|
|
578
|
+
|
|
579
|
+
<details>
|
|
580
|
+
<summary><strong>Options Auto-Restart Tests</strong></summary>
|
|
581
|
+
|
|
582
|
+
- Restart watch when enableHighAccuracy changes
|
|
583
|
+
- Restart watch when timeout changes
|
|
584
|
+
- Not restart watch when watch is false
|
|
585
|
+
|
|
586
|
+
</details>
|
|
587
|
+
|
|
588
|
+
<details>
|
|
589
|
+
<summary><strong>Function Reference Stability Tests</strong></summary>
|
|
590
|
+
|
|
591
|
+
- Maintain stable function references across renders
|
|
592
|
+
- Update distanceFrom and bearingTo when position changes
|
|
593
|
+
|
|
594
|
+
</details>
|
|
595
|
+
|
|
596
|
+
<details>
|
|
597
|
+
<summary><strong>Cleanup Tests</strong></summary>
|
|
598
|
+
|
|
599
|
+
- Clear watch on unmount
|
|
600
|
+
- Clear watch multiple times safely
|
|
601
|
+
|
|
602
|
+
</details>
|
|
603
|
+
|
|
604
|
+
### Running Tests
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
# Run all tests
|
|
608
|
+
pnpm test
|
|
609
|
+
|
|
610
|
+
# Run tests in watch mode
|
|
611
|
+
pnpm test:watch
|
|
612
|
+
|
|
613
|
+
# Run tests with coverage report
|
|
614
|
+
pnpm test --coverage
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Browser Compatibility
|
|
620
|
+
|
|
621
|
+
This hook uses the [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API), which is supported in:
|
|
622
|
+
|
|
623
|
+
- ✅ Chrome 5+
|
|
624
|
+
- ✅ Firefox 3.5+
|
|
625
|
+
- ✅ Safari 5+
|
|
626
|
+
- ✅ Edge 12+
|
|
627
|
+
- ✅ Opera 10.6+
|
|
628
|
+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
|
|
629
|
+
|
|
630
|
+
**Note:** HTTPS is required for geolocation in most modern browsers (except localhost).
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## License
|
|
635
|
+
|
|
636
|
+
MIT © [mirunamu](https://github.com/mirunamu00)
|
|
637
|
+
|
|
638
|
+
This package is part of the [usefy](https://github.com/mirunamu00/usefy) monorepo.
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
<p align="center">
|
|
643
|
+
<sub>Built with care by the usefy team</sub>
|
|
644
|
+
</p>
|
|
645
|
+
|