useGeolocation
A hook for accessing device location using the Geolocation API
A React hook that provides the device's current geographic location using the Geolocation API. Perfect for building location-aware apps like maps, delivery tracking, fitness apps, or any feature that needs to know where the user is.
Source Code
View the full hook implementation in the Hook Source Code section below.
Permission Required
Important: The browser will prompt the user for permission before
accessing their location. If the user denies permission, the error object
will contain a PERMISSION_DENIED code. Always handle this gracefully in
your UI.
Features
- One-time & Watch Mode - Get location once or continuously track position changes
- High Accuracy Option - Choose between GPS (precise but slow) or Wi-Fi/Cell (fast but less accurate)
- Configurable Timeouts - Set custom timeout and cache age for position requests
- Error Handling - Typed errors with human-readable messages for common failure cases
- Manual Refresh -
refetchfunction to update position on demand - Watch Control -
clearWatchto stop continuous tracking when needed - SSR Safe - Gracefully handles server-side rendering environments
Learn More
Request Location
The simplest usage - get the user's current location when the component mounts. The refetch function lets users manually update their position. The errorMessage provides a human-readable description of any errors.
Geolocation API is not supported in this browser
"use client";
import { useGeolocation } from "@repo/hooks/device/use-geolocation";
import { Button } from "@repo/ui/components/button";
import { MapPin, Loader2, AlertCircle, RefreshCw } from "lucide-react";
/* REQUEST LOCATION - Basic Coordinates Display */
export const Example1 = () => {
const {
latitude,
longitude,
accuracy,
isLoading,
isSupported,
error,
errorMessage,
refetch,
} = useGeolocation();
if (!isSupported) {
return (
<div className="rounded-md border border-yellow-500/50 bg-yellow-500/10 p-4">
<p className="text-sm text-yellow-600 dark:text-yellow-400">
Geolocation API is not supported in this browser
</p>
</div>
);
}
return (
<div className="flex w-full max-w-sm flex-col gap-4">
{/* Location Card */}
<div className="rounded-lg border p-4">
<div className="mb-3 flex items-center gap-2">
<MapPin className="text-primary h-5 w-5" />
<span className="font-medium">Your Location</span>
</div>
{isLoading ? (
<div className="text-muted-foreground flex items-center gap-2 py-4 text-sm">
<Loader2 className="h-4 w-4 animate-spin" />
<span>Getting your location...</span>
</div>
) : error ? (
<div className="flex items-start gap-2 rounded-md bg-red-500/10 p-3 text-sm text-red-600 dark:text-red-400">
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0" />
<span>{errorMessage}</span>
</div>
) : latitude && longitude ? (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">
Latitude
</div>
<div className="font-mono text-lg font-medium">
{latitude.toFixed(6)}°
</div>
</div>
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">
Longitude
</div>
<div className="font-mono text-lg font-medium">
{longitude.toFixed(6)}°
</div>
</div>
</div>
{accuracy && (
<div className="text-muted-foreground text-sm">
Accuracy: ±{Math.round(accuracy)} meters
</div>
)}
</div>
) : (
<div className="text-muted-foreground py-4 text-center text-sm">
Click refresh to get your location
</div>
)}
</div>
{/* Refresh Button */}
<Button
onClick={refetch}
isDisabled={isLoading}
variant="outline"
className="gap-2"
>
<RefreshCw
className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`}
/>
{isLoading ? "Getting Location..." : "Refresh Location"}
</Button>
</div>
);
};
Watch Mode
Enable continuous position updates by setting watch: true. This is useful for tracking movement in real-time, like in a fitness or delivery app. The position history shows how coordinates change as you move.
Use clearWatch() to stop tracking and save battery when you're done.
"use client";
import { useGeolocation } from "@repo/hooks/device/use-geolocation";
import { Button } from "@repo/ui/components/button";
import { useState } from "react";
import { Navigation, Play, Square, MapPin } from "lucide-react";
/* WATCH MODE - Continuous Position Updates */
export const Example2 = () => {
const [isWatching, setIsWatching] = useState(false);
const [history, setHistory] = useState<
Array<{ lat: number; lng: number; time: Date }>
>([]);
const {
latitude,
longitude,
accuracy,
speed,
heading,
isLoading,
error,
clearWatch,
} = useGeolocation({
watch: isWatching,
enableHighAccuracy: true,
});
const startWatching = () => {
setHistory([]);
setIsWatching(true);
};
const stopWatching = () => {
clearWatch();
setIsWatching(false);
};
// Add to history when position updates
if (isWatching && latitude && longitude) {
const lastEntry = history[history.length - 1];
if (
!lastEntry ||
lastEntry.lat !== latitude ||
lastEntry.lng !== longitude
) {
if (history.length < 10) {
setHistory((prev) => [
...prev,
{ lat: latitude, lng: longitude, time: new Date() },
]);
}
}
}
return (
<div className="flex w-full max-w-sm flex-col gap-4">
{/* Current Position */}
<div className="rounded-lg border p-4">
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<Navigation className="text-primary h-5 w-5" />
<span className="font-medium">Live Tracking</span>
</div>
{isWatching && (
<span className="flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400">
<span className="h-2 w-2 animate-pulse rounded-full bg-green-500" />
Active
</span>
)}
</div>
{error ? (
<div className="text-sm text-red-500">{error.message}</div>
) : latitude && longitude ? (
<div className="space-y-3">
<div className="font-mono text-sm">
{latitude.toFixed(6)}, {longitude.toFixed(6)}
</div>
<div className="grid grid-cols-3 gap-2 text-center text-xs">
<div className="bg-muted rounded p-2">
<div className="text-muted-foreground">
Accuracy
</div>
<div className="font-medium">
{accuracy
? `±${Math.round(accuracy)}m`
: "—"}
</div>
</div>
<div className="bg-muted rounded p-2">
<div className="text-muted-foreground">
Speed
</div>
<div className="font-medium">
{speed
? `${(speed * 3.6).toFixed(1)} km/h`
: "—"}
</div>
</div>
<div className="bg-muted rounded p-2">
<div className="text-muted-foreground">
Heading
</div>
<div className="font-medium">
{heading ? `${Math.round(heading)}°` : "—"}
</div>
</div>
</div>
</div>
) : (
<div className="text-muted-foreground py-4 text-center text-sm">
{isLoading
? "Getting location..."
: "Start tracking to see position"}
</div>
)}
</div>
{/* History */}
{history.length > 0 && (
<div className="rounded-lg border p-3">
<div className="text-muted-foreground mb-2 text-xs font-medium uppercase">
Position History ({history.length})
</div>
<div className="max-h-32 space-y-1 overflow-y-auto text-xs">
{history.map((entry, i) => (
<div
key={i}
className="bg-muted/50 flex items-center gap-2 rounded px-2 py-1"
>
<MapPin className="text-muted-foreground h-3 w-3" />
<span className="font-mono">
{entry.lat.toFixed(4)},{" "}
{entry.lng.toFixed(4)}
</span>
<span className="text-muted-foreground ml-auto">
{entry.time.toLocaleTimeString()}
</span>
</div>
))}
</div>
</div>
)}
{/* Control Button */}
<Button
onClick={isWatching ? stopWatching : startWatching}
variant={isWatching ? "destructive" : "default"}
className="gap-2"
>
{isWatching ? (
<>
<Square className="h-4 w-4" />
Stop Tracking
</>
) : (
<>
<Play className="h-4 w-4" />
Start Tracking
</>
)}
</Button>
</div>
);
};
High Accuracy Mode
Toggle between high accuracy (GPS) and low power (Wi-Fi/Cell) modes. High accuracy uses GPS for precise positioning but is slower and uses more battery. Low power mode uses nearby Wi-Fi networks and cell towers for a faster but less accurate fix.
The accuracy indicator shows how reliable the current position is - green for excellent (±10m), blue for good, yellow for fair, and red for poor accuracy.
Using GPS for precise location (slower, uses more battery)
"use client";
import { useGeolocation } from "@repo/hooks/device/use-geolocation";
import { Button } from "@repo/ui/components/button";
import { useState } from "react";
import { Crosshair, Satellite, Wifi, RefreshCw } from "lucide-react";
/* HIGH ACCURACY - GPS Precision with Accuracy Indicator */
export const Example3 = () => {
const [useHighAccuracy, setUseHighAccuracy] = useState(true);
const {
latitude,
longitude,
accuracy,
altitude,
isLoading,
error,
refetch,
} = useGeolocation({
enableHighAccuracy: useHighAccuracy,
timeout: 15000,
});
const getAccuracyLevel = (acc: number | null) => {
if (!acc) return { label: "Unknown", color: "gray", icon: Crosshair };
if (acc <= 10)
return { label: "Excellent", color: "green", icon: Satellite };
if (acc <= 50) return { label: "Good", color: "blue", icon: Satellite };
if (acc <= 100) return { label: "Fair", color: "yellow", icon: Wifi };
return { label: "Poor", color: "red", icon: Wifi };
};
const accuracyInfo = getAccuracyLevel(accuracy);
const AccuracyIcon = accuracyInfo.icon;
return (
<div className="flex w-full max-w-sm flex-col gap-4">
{/* Accuracy Mode Toggle */}
<div className="flex gap-2">
<Button
variant={useHighAccuracy ? "default" : "outline"}
size="sm"
onClick={() => setUseHighAccuracy(true)}
className="flex-1 gap-2"
>
<Satellite className="h-4 w-4" />
High Accuracy
</Button>
<Button
variant={!useHighAccuracy ? "default" : "outline"}
size="sm"
onClick={() => setUseHighAccuracy(false)}
className="flex-1 gap-2"
>
<Wifi className="h-4 w-4" />
Low Power
</Button>
</div>
{/* Position Card */}
<div className="rounded-lg border p-4">
{error ? (
<div className="text-sm text-red-500">{error.message}</div>
) : latitude && longitude ? (
<div className="space-y-4">
{/* Accuracy Indicator */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<AccuracyIcon
className={`h-5 w-5 ${
accuracyInfo.color === "green"
? "text-green-500"
: accuracyInfo.color === "blue"
? "text-blue-500"
: accuracyInfo.color === "yellow"
? "text-yellow-500"
: accuracyInfo.color === "red"
? "text-red-500"
: "text-gray-500"
}`}
/>
<span className="text-sm font-medium">
{accuracyInfo.label} Accuracy
</span>
</div>
<span className="text-muted-foreground text-sm">
±{accuracy ? Math.round(accuracy) : "—"}m
</span>
</div>
{/* Accuracy Visual Bar */}
<div className="bg-muted h-2 overflow-hidden rounded-full">
<div
className={`h-full transition-all duration-500 ${
accuracyInfo.color === "green"
? "bg-green-500"
: accuracyInfo.color === "blue"
? "bg-blue-500"
: accuracyInfo.color === "yellow"
? "bg-yellow-500"
: accuracyInfo.color === "red"
? "bg-red-500"
: "bg-gray-500"
}`}
style={{
width: accuracy
? `${Math.max(5, 100 - Math.min(accuracy, 100))}%`
: "0%",
}}
/>
</div>
{/* Coordinates */}
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="bg-muted/50 rounded p-2">
<div className="text-muted-foreground text-xs">
Latitude
</div>
<div className="font-mono font-medium">
{latitude.toFixed(6)}°
</div>
</div>
<div className="bg-muted/50 rounded p-2">
<div className="text-muted-foreground text-xs">
Longitude
</div>
<div className="font-mono font-medium">
{longitude.toFixed(6)}°
</div>
</div>
</div>
{/* Altitude (if available) */}
{altitude !== null && (
<div className="text-muted-foreground text-sm">
Altitude: {Math.round(altitude)}m above sea
level
</div>
)}
</div>
) : (
<div className="text-muted-foreground py-6 text-center text-sm">
{isLoading
? "Acquiring GPS signal..."
: "Click refresh to get your position"}
</div>
)}
</div>
{/* Refresh Button */}
<Button onClick={refetch} isDisabled={isLoading} className="gap-2">
<RefreshCw
className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`}
/>
{isLoading ? "Getting Position..." : "Get Position"}
</Button>
{/* Mode Description */}
<p className="text-muted-foreground text-center text-xs">
{useHighAccuracy
? "Using GPS for precise location (slower, uses more battery)"
: "Using Wi-Fi/Cell towers (faster, less accurate)"}
</p>
</div>
);
};
Distance Calculator
A practical example that calculates the distance between your current location and famous landmarks around the world using the Haversine formula. This demonstrates how to combine geolocation data with custom calculations.
"use client";
import { useGeolocation } from "@repo/hooks/device/use-geolocation";
import { Button } from "@repo/ui/components/button";
import { useState, useMemo } from "react";
import { MapPin, Navigation, RotateCcw } from "lucide-react";
/* DISTANCE CALCULATOR - Distance from Reference Point */
// Famous landmarks for demo
const LANDMARKS = [
{ name: "Eiffel Tower", lat: 48.8584, lng: 2.2945 },
{ name: "Statue of Liberty", lat: 40.6892, lng: -74.0445 },
{ name: "Sydney Opera House", lat: -33.8568, lng: 151.2153 },
{ name: "Taj Mahal", lat: 27.1751, lng: 78.0421 },
{ name: "Big Ben", lat: 51.5007, lng: -0.1246 },
];
// Haversine formula to calculate distance between two points
function calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number,
): number {
const R = 6371; // Earth's radius in km
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function formatDistance(km: number): string {
if (km < 1) {
return `${Math.round(km * 1000)} m`;
}
if (km < 10) {
return `${km.toFixed(1)} km`;
}
return `${Math.round(km).toLocaleString()} km`;
}
export const Example4 = () => {
const { latitude, longitude, isLoading, error, refetch } = useGeolocation();
const [selectedLandmark, setSelectedLandmark] = useState(LANDMARKS[0]);
const distance = useMemo(() => {
if (!latitude || !longitude || !selectedLandmark) return null;
return calculateDistance(
latitude,
longitude,
selectedLandmark.lat,
selectedLandmark.lng,
);
}, [latitude, longitude, selectedLandmark]);
return (
<div className="flex w-full max-w-sm flex-col gap-4">
{/* Landmark Selector */}
<div className="flex flex-col gap-1.5">
<label className="text-muted-foreground text-sm font-medium">
Select a Landmark
</label>
<div className="relative">
<select
value={selectedLandmark?.name}
onChange={(e) => {
const landmark = LANDMARKS.find(
(l) => l.name === e.target.value,
);
if (landmark) setSelectedLandmark(landmark);
}}
className="border-input bg-background focus:ring-ring w-full appearance-none rounded-md border py-2 pl-3 pr-10 text-sm focus:outline-none focus:ring-2"
>
{LANDMARKS.map((landmark) => (
<option key={landmark.name} value={landmark.name}>
{landmark.name}
</option>
))}
</select>
<MapPin className="text-muted-foreground pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2" />
</div>
</div>
{/* Distance Display */}
<div className="rounded-lg border p-4">
{error ? (
<div className="text-sm text-red-500">
Could not get your location
</div>
) : latitude && longitude && distance !== null ? (
<div className="space-y-4 text-center">
<div>
<div className="text-muted-foreground text-sm">
Distance to {selectedLandmark?.name}
</div>
<div className="mt-1 text-3xl font-bold">
{formatDistance(distance)}
</div>
</div>
{/* Visual representation */}
<div className="flex items-center justify-center gap-3">
<div className="flex flex-col items-center">
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center rounded-full">
<Navigation className="text-primary h-5 w-5" />
</div>
<span className="text-muted-foreground mt-1 text-xs">
You
</span>
</div>
<div className="border-muted-foreground/30 flex-1 border-t-2 border-dashed" />
<div className="flex flex-col items-center">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-red-500/10">
<MapPin className="h-5 w-5 text-red-500" />
</div>
<span className="text-muted-foreground mt-1 text-xs">
Landmark
</span>
</div>
</div>
{/* Coordinates */}
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="bg-muted/50 rounded p-2">
<div className="text-muted-foreground">
Your Position
</div>
<div className="font-mono">
{latitude.toFixed(4)},{" "}
{longitude.toFixed(4)}
</div>
</div>
<div className="bg-muted/50 rounded p-2">
<div className="text-muted-foreground">
Landmark
</div>
<div className="font-mono">
{selectedLandmark?.lat.toFixed(4)},{" "}
{selectedLandmark?.lng.toFixed(4)}
</div>
</div>
</div>
</div>
) : (
<div className="text-muted-foreground py-6 text-center text-sm">
{isLoading
? "Getting your location..."
: "Get your location to calculate distance"}
</div>
)}
</div>
{/* Refresh Button */}
<Button
onClick={refetch}
isDisabled={isLoading}
variant="outline"
className="gap-2"
>
<RotateCcw
className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`}
/>
{isLoading ? "Getting Location..." : "Update My Location"}
</Button>
</div>
);
};
API Reference
Hook Signature
function useGeolocation(options?: UseGeolocationOptions): GeolocationState;Options
Configure the hook behavior by passing an options object:
| Property | Type | Default | Description |
|---|---|---|---|
watch | boolean | false | When true, continuously track position changes instead of a one-time request |
enableHighAccuracy | boolean | false | When true, use GPS for precise location (slower, more battery). Otherwise uses Wi-Fi/Cell data |
timeout | number | Infinity | Maximum time in milliseconds to wait for a position before timing out |
maximumAge | number | 0 | Maximum age in milliseconds of a cached position. Set to 0 to always fetch fresh |
Return Value
The hook returns an object with position data and control functions:
| Property | Type | Description |
|---|---|---|
latitude | number | null | Latitude in decimal degrees (e.g., 37.7749 for San Francisco) |
longitude | number | null | Longitude in decimal degrees (e.g., -122.4194 for San Francisco) |
accuracy | number | null | Position accuracy in meters (lower is better) |
altitude | number | null | Altitude in meters above sea level (null if not available) |
altitudeAccuracy | number | null | Accuracy of the altitude value in meters |
heading | number | null | Direction of travel in degrees (0-360, null if stationary) |
speed | number | null | Speed in meters per second (null if stationary) |
timestamp | number | null | Unix timestamp when the position was acquired |
isLoading | boolean | true while waiting for a position |
isSupported | boolean | false if Geolocation API is not available |
error | GeolocationPositionError | null | Browser error object if position request failed |
errorMessage | string | null | Human-readable error message for common error codes |
refetch | () => void | Manually request a position update |
clearWatch | () => void | Stop watching for position changes (only in watch mode) |
Error Codes
When an error occurs, the error object contains a code property:
| Code | Value | Description |
|---|---|---|
PERMISSION_DENIED | 1 | User denied the location permission request |
POSITION_UNAVAILABLE | 2 | Device cannot determine position (no GPS, no network, etc.) |
TIMEOUT | 3 | Position request timed out before a fix could be obtained |
Hook Source Code
import { useState, useEffect, useCallback, useRef } from "react";
/**
* Options for the useGeolocation hook
*/
export interface UseGeolocationOptions {
/** Enable continuous position updates (default: false) */
watch?: boolean;
/** Use high accuracy GPS (slower, more battery) vs Wi-Fi/Cell (faster, less accurate) */
enableHighAccuracy?: boolean;
/** Maximum age of a cached position in milliseconds (default: 0 - always fetch fresh) */
maximumAge?: number;
/** Timeout for position request in milliseconds (default: Infinity) */
timeout?: number;
}
/**
* Geolocation state returned by the hook
*/
export interface GeolocationState {
/** Latitude in decimal degrees (e.g., 37.7749) */
latitude: number | null;
/** Longitude in decimal degrees (e.g., -122.4194) */
longitude: number | null;
/** Accuracy of the position in meters */
accuracy: number | null;
/** Altitude in meters above sea level (null if not available) */
altitude: number | null;
/** Accuracy of the altitude in meters (null if not available) */
altitudeAccuracy: number | null;
/** Direction of travel in degrees (0-360, null if not moving) */
heading: number | null;
/** Speed in meters per second (null if not moving) */
speed: number | null;
/** Timestamp when the position was acquired */
timestamp: number | null;
/** Whether the position is currently being fetched */
isLoading: boolean;
/** Whether the Geolocation API is supported */
isSupported: boolean;
/** Error object if position request failed */
error: GeolocationPositionError | null;
/** Error message for display (human-readable) */
errorMessage: string | null;
/** Manually request a position update */
refetch: () => void;
/** Stop watching for position updates (only in watch mode) */
clearWatch: () => void;
}
/**
* Human-readable error messages for GeolocationPositionError codes
*/
function getErrorMessage(error: GeolocationPositionError): string {
switch (error.code) {
case error.PERMISSION_DENIED:
return "Location permission denied. Please allow access in your browser settings.";
case error.POSITION_UNAVAILABLE:
return "Position unavailable. Your device may not have GPS or location services enabled.";
case error.TIMEOUT:
return "Location request timed out. Please try again.";
default:
return "An unknown error occurred while getting your location.";
}
}
const initialState: Omit<GeolocationState, "refetch" | "clearWatch"> = {
latitude: null,
longitude: null,
accuracy: null,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
timestamp: null,
isLoading: false,
isSupported: true,
error: null,
errorMessage: null,
};
/**
* A React hook that provides the device's current geographic location using the
* Geolocation API. Supports one-time position requests and continuous watching.
*
* @param options - Configuration options for the hook
* @returns GeolocationState object with position data and control functions
*
* @example
* ```tsx
* // One-time position request
* const { latitude, longitude, isLoading, error } = useGeolocation();
*
* // Continuous position updates
* const { latitude, longitude } = useGeolocation({ watch: true });
*
* // High accuracy mode
* const { latitude, longitude, accuracy } = useGeolocation({
* enableHighAccuracy: true,
* timeout: 10000
* });
* ```
*/
export function useGeolocation(
options: UseGeolocationOptions = {},
): GeolocationState {
const {
watch = false,
enableHighAccuracy = false,
maximumAge = 0,
timeout = Infinity,
} = options;
const [state, setState] = useState(initialState);
const watchIdRef = useRef<number | null>(null);
// Check if API is supported
const isSupported =
typeof navigator !== "undefined" && "geolocation" in navigator;
// Success handler
const handleSuccess = useCallback((position: GeolocationPosition) => {
const { coords, timestamp } = position;
setState({
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
altitude: coords.altitude,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
speed: coords.speed,
timestamp,
isLoading: false,
isSupported: true,
error: null,
errorMessage: null,
});
}, []);
// Error handler
const handleError = useCallback((error: GeolocationPositionError) => {
setState((prev) => ({
...prev,
isLoading: false,
error,
errorMessage: getErrorMessage(error),
}));
}, []);
// Position options
const positionOptions: PositionOptions = {
enableHighAccuracy,
maximumAge,
timeout: timeout === Infinity ? undefined : timeout,
};
// Refetch function (manual position request)
const refetch = useCallback(() => {
if (!isSupported) return;
setState((prev) => ({
...prev,
isLoading: true,
error: null,
errorMessage: null,
}));
navigator.geolocation.getCurrentPosition(
handleSuccess,
handleError,
positionOptions,
);
}, [
isSupported,
handleSuccess,
handleError,
enableHighAccuracy,
maximumAge,
timeout,
]);
// Clear watch function
const clearWatch = useCallback(() => {
if (watchIdRef.current !== null) {
navigator.geolocation.clearWatch(watchIdRef.current);
watchIdRef.current = null;
}
}, []);
useEffect(() => {
if (!isSupported) {
setState((prev) => ({
...prev,
isSupported: false,
isLoading: false,
}));
return;
}
setState((prev) => ({ ...prev, isLoading: true }));
if (watch) {
// Watch mode - continuous updates
watchIdRef.current = navigator.geolocation.watchPosition(
handleSuccess,
handleError,
positionOptions,
);
} else {
// One-time position request
navigator.geolocation.getCurrentPosition(
handleSuccess,
handleError,
positionOptions,
);
}
return () => {
if (watchIdRef.current !== null) {
navigator.geolocation.clearWatch(watchIdRef.current);
watchIdRef.current = null;
}
};
}, [
isSupported,
watch,
handleSuccess,
handleError,
enableHighAccuracy,
maximumAge,
timeout,
]);
return {
...state,
isSupported,
refetch,
clearWatch,
};
}
export default useGeolocation;