A React hook for accessing geolocation with real-time tracking and distance calculation
npm install @usefy/use-geolocation

A powerful React hook for accessing device geolocation with real-time tracking and distance calculation
Installation •
Quick Start •
API Reference •
Examples •
License
---
@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.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
- Zero Dependencies — Pure React implementation with no external dependencies
- TypeScript First — Full type safety with comprehensive type definitions
- Real-Time Tracking — Watch position changes as device moves
- Distance Calculation — Built-in Haversine formula for accurate distance calculations
- Bearing Calculation — Calculate direction/bearing between coordinates
- Permission Tracking — Monitor geolocation permission state changes
- Error Handling — Comprehensive error handling with typed error codes
- SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
- Stable References — Memoized functions for optimal performance
- Well Tested — Comprehensive test coverage with Vitest
---
``bashnpm
npm install @usefy/use-geolocation
$3
This package requires React 18 or 19:
`json
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}
`---
Quick Start
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function MyLocation() {
const { position, loading, error } = useGeolocation();
if (loading) return
Loading location...
;
if (error) return Error: {error.message}
;
if (!position) return No position yet
; return (
Latitude: {position.coords.latitude}
Longitude: {position.coords.longitude}
Accuracy: {position.coords.accuracy}m
);
}
`---
API Reference
$3
A hook that manages geolocation state with real-time tracking and utility functions.
#### Parameters
| Parameter | Type | Description |
| --------- | ----------------------- | ----------------------------- |
|
options | UseGeolocationOptions | Optional configuration object |#### Options
| Option | Type | Default | Description |
| -------------------- | ----------------------------------- | ------- | ----------------------------------------------------------- |
|
enableHighAccuracy | boolean | false | Enable high accuracy mode (uses GPS, consumes more battery) |
| maximumAge | number | 0 | Maximum age of cached position in milliseconds |
| timeout | number | 30000 | Timeout in milliseconds for position request |
| watch | boolean | false | Start watching position immediately on mount |
| immediate | boolean | true | Get initial position immediately on mount |
| onSuccess | (position: GeoPosition) => void | — | Callback when position is successfully retrieved |
| onError | (error: GeolocationError) => void | — | Callback when an error occurs |
| onPositionChange | (position: GeoPosition) => void | — | Callback when position changes (during watch mode) |
| onPermissionChange | (state: PermissionState) => void | — | Callback when permission state changes |#### Returns
UseGeolocationReturn| Property | Type | Description |
| -------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------- |
|
position | GeoPosition \| null | Current position data (null if not yet retrieved) |
| loading | boolean | Loading state (true while fetching position) |
| error | GeolocationError \| null | Error object (null if no error) |
| permission | PermissionState | Current permission state (prompt, granted, denied, unavailable) |
| isSupported | boolean | Whether geolocation is supported in this environment |
| getCurrentPosition | () => void | Manually get current position (one-time request) |
| watchPosition | () => void | Start watching position for real-time updates |
| clearWatch | () => void | Stop watching position |
| distanceFrom | (lat: number, lon: number) => number \| null | Calculate distance from current position to target coordinates in meters |
| bearingTo | (lat: number, lon: number) => number \| null | Calculate bearing/direction from current position to target coordinates (0-360 degrees) |#### Error Codes
| Code | Description |
| ---------------------- | ------------------------------------------------ |
|
PERMISSION_DENIED | User denied geolocation permission |
| POSITION_UNAVAILABLE | Position information unavailable |
| TIMEOUT | Position request timed out |
| NOT_SUPPORTED | Geolocation is not supported in this environment |---
Examples
$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function CurrentLocation() {
const { position, loading, error } = useGeolocation();
if (loading) return
Loading location...;
if (error) return Error: {error.message};
if (!position) return No position available; return (
Latitude: {position.coords.latitude}
Longitude: {position.coords.longitude}
Accuracy: {position.coords.accuracy}m
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function ManualLocation() {
const {
position,
loading,
error,
getCurrentPosition,
watchPosition,
clearWatch,
} = useGeolocation({ immediate: false, watch: false });
return (
{position && (
{position.coords.latitude}, {position.coords.longitude}
)}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function LiveTracking() {
const { position, watchPosition, clearWatch } = useGeolocation({
immediate: false,
watch: false,
onPositionChange: (pos) => {
console.log("Position updated:", pos);
},
});
const [isTracking, setIsTracking] = useState(false);
const handleStart = () => {
watchPosition();
setIsTracking(true);
};
const handleStop = () => {
clearWatch();
setIsTracking(false);
};
return (
{isTracking &&
🔴 Live tracking active
} {position && (
{position.coords.latitude.toFixed(6)},{" "}
{position.coords.longitude.toFixed(6)}
)}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function DistanceToDestination() {
const { position, distanceFrom } = useGeolocation();
// New York City coordinates
const nyLat = 40.7128;
const nyLon = -74.006;
const distance = distanceFrom(nyLat, nyLon);
return (
{distance && Distance to NYC: {(distance / 1000).toFixed(2)} km
}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function DirectionToDestination() {
const { position, bearingTo } = useGeolocation();
// London coordinates
const londonLat = 51.5074;
const londonLon = -0.1278;
const bearing = bearingTo(londonLat, londonLon);
const getDirection = (bearing: number | null) => {
if (bearing === null) return "—";
if (bearing < 22.5 || bearing >= 337.5) return "North";
if (bearing < 67.5) return "Northeast";
if (bearing < 112.5) return "East";
if (bearing < 157.5) return "Southeast";
if (bearing < 202.5) return "South";
if (bearing < 247.5) return "Southwest";
if (bearing < 292.5) return "West";
return "Northwest";
};
return (
{bearing !== null && (
Direction to London: {getDirection(bearing)} ({bearing.toFixed(0)}°)
)}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function HighAccuracyLocation() {
const { position, loading } = useGeolocation({
enableHighAccuracy: true,
timeout: 10000,
});
if (loading) return
Getting high accuracy location...; return (
{position && High accuracy: {position.coords.accuracy.toFixed(1)}m
}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function PermissionStatus() {
const { permission, onPermissionChange } = useGeolocation({
onPermissionChange: (state) => {
console.log("Permission changed:", state);
},
});
return (
Permission: {permission}
{permission === "denied" && (
Please enable location permissions in your browser settings.
)}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function RobustLocation() {
const { position, error, getCurrentPosition } = useGeolocation({
immediate: false,
onError: (err) => {
console.error("Geolocation error:", err.code, err.message);
if (err.code === "PERMISSION_DENIED") {
alert("Please allow location access");
} else if (err.code === "TIMEOUT") {
alert("Location request timed out. Please try again.");
}
},
});
return (
{error && (
Error: {error.code}
{error.message}
)} {position && (
{position.coords.latitude}, {position.coords.longitude}
)}
);
}
`$3
`tsx
import { useGeolocation } from "@usefy/use-geolocation";function AutoTracking() {
const { position, clearWatch } = useGeolocation({
watch: true, // Automatically start watching on mount
immediate: false,
});
return (
Auto-tracking is active
{position && (
{position.coords.latitude.toFixed(6)},{" "}
{position.coords.longitude.toFixed(6)}
)}
);
}
`---
TypeScript
This hook is written in TypeScript and exports comprehensive type definitions.
`tsx
import {
useGeolocation,
type UseGeolocationOptions,
type UseGeolocationReturn,
type GeoPosition,
type GeolocationError,
type PermissionState,
} from "@usefy/use-geolocation";// Full type inference
const geolocation: UseGeolocationReturn = useGeolocation({
enableHighAccuracy: true,
timeout: 10000,
onSuccess: (position: GeoPosition) => {
console.log("Got position:", position);
},
onError: (error: GeolocationError) => {
console.error("Error:", error.code, error.message);
},
});
// Permission state is typed as union
const permission: PermissionState = geolocation.permission;
// "prompt" | "granted" | "denied" | "unavailable"
`---
Performance
- Stable Function References — All control functions are memoized with
useCallback
- Smart Re-renders — Only re-renders when position, loading, or error state changes
- Automatic Cleanup — Watch is automatically cleared on unmount
- Options Auto-Restart — Watch automatically restarts when options change`tsx
const { getCurrentPosition, watchPosition, clearWatch } = useGeolocation();// These references remain stable across renders
useEffect(() => {
// Safe to use as dependencies
}, [getCurrentPosition, watchPosition, clearWatch]);
``---
This package maintains comprehensive test coverage to ensure reliability and stability.
📊 View Detailed Coverage Report (GitHub Pages)
Initialization Tests
- Initialize with correct default state
- Detect unsupported environment
- Set loading state when immediate is true
getCurrentPosition Tests
- Get current position successfully
- Handle PERMISSION_DENIED error
- Handle POSITION_UNAVAILABLE error
- Handle TIMEOUT error
- Handle NOT_SUPPORTED error when geolocation unavailable
watchPosition Tests
- Watch position and receive updates
- Clear watch on clearWatch call
- Clear existing watch before starting new one
- Call onPositionChange callback on updates
Permission State Tests
- Track permission state changes
- Set permission to unavailable when permissions API fails
- Call onPermissionChange callback when permission changes
Utility Functions Tests
- Calculate distance correctly using Haversine formula
- Return null when no position available for distanceFrom
- Calculate bearing correctly
- Return null when no position available for bearingTo
Options Tests
- Use custom options (enableHighAccuracy, timeout, maximumAge)
- Use default timeout of 30000ms
- Call onSuccess callback
- Call onError callback
Auto-Watch and Immediate Tests
- Start watching immediately when watch: true
- Get position immediately when immediate: true
- Not fetch position when immediate: false
Options Auto-Restart Tests
- Restart watch when enableHighAccuracy changes
- Restart watch when timeout changes
- Not restart watch when watch is false
Function Reference Stability Tests
- Maintain stable function references across renders
- Update distanceFrom and bearingTo when position changes
Cleanup Tests
- Clear watch on unmount
- Clear watch multiple times safely
---
This hook uses the Geolocation API, which is supported in:
- ✅ Chrome 5+
- ✅ Firefox 3.5+
- ✅ Safari 5+
- ✅ Edge 12+
- ✅ Opera 10.6+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
Note: HTTPS is required for geolocation in most modern browsers (except localhost).
---
MIT © mirunamu
This package is part of the usefy monorepo.
---
Built with care by the usefy team