There are some interesting and useful Web APIs built into the browsers and Geolocation API is one of them. It is used to determine the users location using longitude and latitude coordinates. However there are some difficulties which you will need to count:
- It's asynchronous
- Needs permission from the user
- Decide which approach will you need
How to use the API?
Once you verified that the target browser supports it then you can access the location data by calling the navigator.geolocation
's getCurrentPosition
method or by assigning a location watch listener using the watchPosition
. The last one will emit the results if the device location has changed.
It's async. This means that if you call the API it will take unpredictable time for the device to return the current coordinates from the user's location. You can handle this via callbacks:
const successHandler = position => console.log(position.coord);
const errorHandler = error => console.error(error.message);
navigator.geolocation.getCurrentPosition(successHandler, errorHandler);
// Or using the watch method:
const locationWatchId = navigator.geolocation.watchPosition(successHandler, errorHandler);
To listen the location changes you can use the
watchPosition
method, but don't forget to clear the listener if you don't need it anymore to avoid memory leaks. You can do that by calling theclearWatch
method with the listener id:
navigator.geolocation.clearWatch(locationWatchId);
Options
You can provide few options to it:
-
enableHighAccuracy
: The API can provide more accurate coordinates, but it costs in slower response time. -
timeout
: You can set the response timeout in milliseconds which means that it will call the error callback if the device doesn't send any location information during that time. -
maximumAge
: You are able to set the time in milliseconds while the API can return the values from the cache
Usage:
export const geolocationOptions = {
enableHighAccuracy: true,
timeout: 1000 * 60 * 1, // 1 min (1000 ms * 60 sec * 1 minute = 60 000ms)
maximumAge: 1000 * 3600 * 24 // 24 hour
};
navigator.geolocation.getCurrentPosition(successHandler, errorHandler, geolocationOptions);
How can you use it in your custom React Hooks?
*This section requires a basic understanding how React hooks works.
I have prepared an example using the basic Create React App starter to demonstrate how this native Web API works with React Hooks. You can find the full source code here: Open the Repo
Using the current location
Create a custom hook which will call the Geolocation API using the getCurrentPosition
method.
First you need to verify if the geolocation is supported in the target browser. To do this you can check if the navigator
has the geolocation
property. Later you can display user friendly errors, but to do this you should create a local state to hold the possible error messages.
const useCurrentLocation = () => {
// store error message in state
const [error, setError] = useState();
useEffect(() => {
// If the geolocation is not defined in the used browser you can handle it as an error
if (!navigator.geolocation) {
setError('Geolocation is not supported.');
return;
}
}, []);
};
You have to prepare your custom hook to store the Geolocation API results locally, so you should extend your state with another value called location
and expose it from the hook for further usage.
const useCurrentLocation = () => {
// ...
// store location in state
const [location, setLocation] = useState();
// ...
// Expose location result and the possible error message
return { location, error };
};
Okay, so you have prepared everything to call the actual Geolocation API method. Right?
Not yet.
You will need to pass the success and error callbacks to the getCurrentPosition
method, but you haven't created these handlers yet. Let's do that:
const useCurrentLocation = () => {
// ...
// Success handler for geolocation's `getCurrentPosition` method
const handleSuccess = position => {
const { latitude, longitude } = position.coords;
setLocation({
latitude,
longitude
});
};
// Error handler for geolocation's `getCurrentPosition` method
const handleError = error => {
setError(error.message);
};
// ...
};
The handleSuccess
will destruct the longitude and latitude values from the location results and set the new values into your local state variable.
The handleError
will set any error message coming from the Geolocation API into the local error state variable. This callback is called when getting the location times out or the user denies the asked permissions.
Now you are ready to call what you need to retrieve the user's location. To do this you can call the above mentioned getCurrentPosition
method inside your useEffect
hook.
useEffect(() => {
// ...
// Call the Geolocation API
navigator.geolocation.getCurrentPosition(handleSuccess, handleError);
// ...
}, []);
You can add some additional behavior to your newly created custom hook with Geolocation options. I decided to pass these settings as an optional object parameter to the custom hook.
Note: You use geolocation inside
useEffect
and if you would like to pass your options argument to thegetCurrentPosition
method then you have to add it to theuseEffect
hook's dependency list. This will help you to rerun the location API again with the new options if the reference of this parameter changes.
const useCurrentLocation = (options = {}) => {
// ...
useEffect(() => {
// ...
// Call the Geolocation API with options
navigator.geolocation.getCurrentPosition(handleSuccess, handleError, options);
// ...
// Add options parameter to the dependency list
}, [options]);
// ...
};
Usage of the useCurrentLocation
hook
const geolocationOptions = {
// Using this option you can define when should the location request timeout and
// call the error callback with timeout message.
timeout: 1000 * 60 * 1 // 1 min (1000 ms * 60 sec * 1 minute = 60 000ms)
};
function App() {
const { location, error } = useCurrentLocation(geolocationOptions);
return (
<div>
<h1>HTML Geolocation API with React Hooks example</h1>;
{location ? (
<code>
Latitude: {location.latitude}, Longitude: {location.longitude}
</code>
) : (
<p>Loading...</p>
)}
{error && <p>Location Error: {error}</p>}
</div>
);
}
See the result in the browser:
Listen to device location changes
What if you need to track the user's location while they are using your application?
This would be the right time to choose the watchPosition
over the getCurrentLocation
. However it can be useful any time when you need to interrupt the location request manually.
For example: if your application makes possible for the users to set the position manually while you already started to access their location automatically.
This is not possible when you are using the current location request, but you can save yourself with the provided location listener. You can prepare for these cases combined the watchPosition
and the clearWatch
methods.
To achieve this functionality you just need to do some minor modifications on the previously created useCurrentLocation
custom hook.
First what you need is create a ref variable in the custom hook. This will help you to keep the reference of the listener instance by assigning the returned value of the watchPosition
to it.
const useWatchLocation = (options = {}) => {
// ...
// save the returned id from the geolocation's `watchPosition`
// to be able to cancel the watch instance.
const locationWatchId = useRef(null);
// ...
};
Assign the location listener inside useEffect
similar like you did for the getCurrentLocation
.
const useWatchLocation = (options = {}) => {
// ...
useEffect(() => {
// ...
// Start to watch the location with the Geolocation API
locationWatchId.current = navigator.geolocation.watchPosition(handleSuccess, handleError, options);
// ...
}, [options]);
// ...
};
Last thing you have to do is implement the clear up logic. React can unmount components which are using this location hook. To prepare for this case you have to pay attention to clear every listener to avoid any memory leaks in your application. You can reach this by extending the useEffect
with its returned function. This can be defined by you, so in this case the returned function will call the clearWatch
method from the Geolocation API using the tracked listener id.
Also if you need to cancel the location tracking programmatically, then you can expose the implemented clear up function from your custom hook.
const useWatchLocation = (options = {}) => {
// ...
// Clears the watch instance based on the saved watch id
const cancelLocationWatch = () => {
if (locationWatchId.current && navigator.geolocation) {
navigator.geolocation.clearWatch(locationWatchId.current);
}
};
useEffect(() => {
// ...
// Clear the location watch instance when React unmounts the used component
return cancelLocationWatch;
}, [options]);
// ...
// Exposed results and public cancel method to clear the location listener manually.
return { location, cancelLocationWatch, error };
};
Usage of the useWatchLocation
hook
function App() {
const { location, cancelLocationWatch, error } = useWatchLocation();
useEffect(() => {
if (!location) return;
// Cancel location watch after 3sec once the location was found.
setTimeout(() => {
cancelLocationWatch();
}, 3000);
}, [location, cancelLocationWatch]);
// ...
}
Source code: Open the Repo. Feel free to check out and play around with it. You can extend the code with your custom logic, optimize it to avoid unnecessary rerenders in your components etc.
You can read more about the Geolocation API in the docs.
Top comments (0)