How toUse SWR with Geolocation
While SWR is mostly used to fetch data from an API, it could be used to read data from any source, in this case, we will create a fetcher
function to get the current position in latitude and longitude of the user.
We will also subscribe to location changes and update the data SWR used to ensure we keep it up-to-date.
Running Demo
This is the final project running in CodeSandbox
<iframe src="https://codesandbox.io/embed/use-swr-with-geolocation-d3l7h?fontsize=14&hidenavigation=1&theme=light&view=preview" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden;" title="Use SWR with Geolocation" allow="geolocation" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
</iframe>
Creating the Fetcher
First of all, we need to create the fetcher
we will pass to SWR, this function must return a Promise resolved to the data we want SWR to cache. However, Geolocation API uses a callback, to convert it to a Promise we could return an instance of Promise and manually resolve it when we get the location.
function fetcher() {
return new Promise((resolve, reject) => {
function onSuccess({ coords }) {
resolve([coords.latitude, coords.longitude]);
}
navigator.geolocation.getCurrentPosition(onSuccess, reject);
});
}
As you can see, the onSuccess
callback we pass to navigator.geolocation.getCurrentPosition
will resolve the Promise with an array with the latitude and longitude.
Using SWR in a Component
Now we need to use our fetcher in a component. First, we need to create a component where we will call useSWR
as the key we will use geolocation
this key is not going to be used by the fetcher in our case, as you would typically do if you used an API endpoint, as the fetcher
we will use our function from above.
function App() {
const { data: position, mutate } = useSWR("geolocation", fetcher);
if (!position) {
return (
<Map center={[0, 0]} zoom={15}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
</Map>
);
}
return (
<Map center={position} zoom={15}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
<Marker position={position} />
</Map>
);
}
As you can see, we detect if the position
exists and render a centered map with a marker if it exists and a non-centered map without a marker, if it does not, exists.
Note: The Map, TileLayer and Marker components come from the react-leaflet library, but could be easily replaced with Google Map components if desired. I have used this one because it does not need any API key to work.
Subscribing to Location Changes
To subscribe to changes in the current location, we could use the navigator.geolocation.watchPosition
function, and this function receives a callback similar to navigator.geolocation.getCurrentPosition
, we could then use the mutate
function from SWR to update the cached data.
To run that function, we could use the useEffect hook of React.
React.useEffect(() => {
const id = navigator.geolocation.watchPosition((position) => {
mutate([position.coords.latitude, position.coords.longitude], false);
});
return () => navigator.geolocation.clearWatch(id);
}, [mutate]);
Notice we are getting an id from watchPosition
, this id is the watcher's identification. We later use it in the returned function to unsubscribe. This let us stop calling mutate
after the component unmounts.
We also call mutate
without passing the key, this is because useSWR
will also return a mutate
function (you can see it in the step before) with the key
predefined, so we only need to pass the rest of the arguments.
In our case we disable the revalidation because we can trust the data we received, there is no need to call getCurrentPosition
again after a new position comes from watchPosition
.
All Combined
If we combined all the code above, we will get the following code:
import React from "react";
import useSWR from "swr";
import { Map, TileLayer, Marker } from "react-leaflet"; // the Map library
// Our fetcher function
function fetcher() {
return new Promise((resolve, reject) => {
function onSuccess({ coords }) {
resolve([coords.latitude, coords.longitude]);
}
navigator.geolocation.getCurrentPosition(onSuccess, reject);
});
}
export default function App() {
const { data: position, mutate } = useSWR("geolocation", fetcher);
// Our effect is defined after useSWR and before the condition
React.useEffect(() => {
const id = navigator.geolocation.watchPosition((position) => {
mutate([position.coords.latitude, position.coords.longitude], false);
});
return () => navigator.geolocation.clearWatch(id);
}, [mutate]);
if (!position) {
return (
<Map center={[0, 0]} zoom={15}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
</Map>
);
}
return (
<Map center={position} zoom={15}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
<Marker position={position} />
</Map>
);
}
You can see the application working in the demo at the beginning of the article, if you are on Google Chrome (or another Chromium-based browser) you can mock your sensors in the DevTools to simulate being in another part of the world, and the map will update in real-time.
You can also open it in your mobile phone and go out for a walk to see how you move, the amount of distance and accuracy of your location will vary per physical device.