🌍 Creating React usePosition() hook for getting browser's geolocation
Tags: webdev, javascript, react, tutorial
Published: 2019-06-30 09:01:33

---
title: 🌍 Creating React usePosition() hook for getting browser's geolocation
published: true
description: Decoupling of geolocation logic into React hook.
tags: webdev, javascript, react, tutorial
---

<div dir="ltr" class="reader-article-content"><h3>TL;DR</h3><p>In this article we’ll create a React <em>usePosition()</em> hook to fetch and follow browser’s location. Under the hood we’ll use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition" target="_blank" rel="nofollow noopener"><em>getCurrentPosition</em></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition" target="_blank" rel="nofollow noopener"><em>watchPosition</em></a> functions that are provided by global object <em>navigator.geolocation</em>. The final version of the <em>usePosition()</em> hook is <a href="https://github.com/trekhleb/use-position" target="_blank" rel="nofollow noopener"><strong><em>published on GitHub</em></strong></a> and <a href="https://www.npmjs.com/package/use-position" target="_blank" rel="nofollow noopener"><strong><em>NPM</em></strong></a> and is ready to be consumed by your apps.</p><h3>Why we might need usePosition() hook at&nbsp;all</h3><p>One of the advantages of <a href="https://reactjs.org/docs/hooks-intro.html" target="_blank" rel="nofollow noopener">React hooks</a> is <em>ability to separate concerns</em>. Instead of having a state object with, let’s say, geo-position <strong>and</strong> socket connection we might avoid using state at all and just use two different hooks that will handle the state management for us. Even more, instead of initiating browser position watcher <strong>and</strong> opening a socket connection in the same <em>componentDidMount()</em> callback we might split this logic into two independent hooks. This gives us cleaner and more maintainable code.</p><h3>How we’re going to usePosition() hook</h3><p>Let’s do some reverse engineering and imagine that we already have a <em>usePosition()</em> hook implemented. Here is how we might want to use it:</p>

<i>⚠️ All code samples below use wrong symbol =≻ instead of => for  lambda functions annotations. Be aware of this while copying the code and trying to execute it since it might not work. Please replace ≻ with > manually in your code. For some reason I had issues with pasting the > symbol inside the code block. Sorry for inconvenience.</i>

```javascript
import React from 'react';
import {usePosition} from './usePosition';
export const UsePositionDemo = () =≻ {
  const {latitude, longitude, error} = usePosition();
  return (
    <code>
      latitude: {latitude}<br/>
      longitude: {longitude}<br/>
      error: {error}
    </code>
  );
};
```


<p>You see, it is only one line with <em>usePosition()</em> hook and you already have the data (<em>latitude</em> and <em>longitude</em>). We don’t even use <em>useState()</em> and <em>useEffect()</em> here. Position subscription and watcher cleanup is incapsulated in <em>usePosition()</em> hook. Now the redraw component magic will be handled for us by React and we will see the <em>&lt;code&gt;…&lt;/code&gt;</em> block constantly being updated with the latest position value of the browser. Looks pretty neat and clean.</p><h3>usePosition() hook implementation</h3><p>Our custom <em>usePosition()</em> hook is just a JavaScript function that uses other hooks like <a href="https://reactjs.org/docs/hooks-state.html" target="_blank" rel="nofollow noopener"><em>useState()</em></a> and <a href="https://reactjs.org/docs/hooks-effect.html" target="_blank" rel="nofollow noopener"><em>useEffect()</em></a>. It will look something like:</p>

```javascript
// imports go here...
export const usePosition = () =≻ {
  // code goes here...
}
```

<p>We will use <em>useEffect()</em> hook to hook to the moment in time when a component (that will consume our hook) is rendered and to subscribe to geolocation changes. We will also use <em>useState()</em> hook to store <em>latitude</em>, <em>longitude</em> and <em>error</em> message (in case if user won’t allow browser to share its position). So we need to import these hooks first:</p>

```javascript
import {useState, useEffect} from 'react';
export const usePosition = () =≻ {
  // code goes here...
}
```

<p>Let’s init a storage for position and for error:</p>

```javascript
import {useState, useEffect} from 'react';
export const usePosition = () =≻ {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);
  
  // other code goes here...
}
```

<p>Let’s return a desirable values from the function. We don’t have them yet but let’s return initial values so far and fill them later:</p>

```javascript
import {useState, useEffect} from 'react';
export const usePosition = () =≻ {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);
  
  // other code goes here...
  return {...position, error};
}
```

<p>Here is a key part of our hook — fetching the browser’s position. We will execute fetching logic after component has been rendered (useEffect hook).&nbsp;</p>

```javascript
import {useState, useEffect} from 'react';
export const usePosition = () =≻ {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);
  
  // callbacks will go here...
  useEffect(() =≻ {
    const geo = navigator.geolocation;
    if (!geo) {
      setError('Geolocation is not supported');
      return;
    }
    watcher = geo.watchPosition(onChange, onError);
    return () =≻ geo.clearWatch(watcher);
  }, []);
  return {...position, error};
}
```

<p>In useEffect() hook we first do some checks to see if the browser is supporting <em>navigator.geolocation</em>. If geolocation is not supported we’re setting up an error and returning from the effect. In case if <em>navigator.geolocation</em> is supported we subscribe to position changes by providing an <em>onChange()</em> and <em>onError()</em> callbacks (we’ll add them in a moment). Notice that we’re returning a lambda function from <em>useEffect()</em>. In that lambda function we are clearing the watcher once the component is unmounted. So this subscribe/unsubscribe logic will be handled internally by our <em>usePosition()</em> hook and consumers shouldn’t worry about it.</p><p>Let’s now add missing callbacks:</p>

```javascript
import {useState, useEffect} from 'react';
export const usePosition = () =≻ {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);
  
  const onChange = ({coords}) =≻ {
    setPosition({
      latitude: coords.latitude,
      longitude: coords.longitude,
    });
  };
  const onError = (error) =≻ {
    setError(error.message);
  };
  useEffect(() =≻ {
    const geo = navigator.geolocation;
    if (!geo) {
      setError('Geolocation is not supported');
      return;
    }
    watcher = geo.watchPosition(onChange, onError);
    return () =≻ geo.clearWatch(watcher);
  }, []);
  return {...position, error};
}
```

<p>And we’re done. The hook <em>usePosition()</em> may be consumed and it incapsulates only geolocation related logic.</p><h3>Afterword</h3><p>You may find a <a href="https://trekhleb.github.io/use-position/" target="_blank" rel="nofollow noopener">demo</a> and more detailed implementation of <a href="https://github.com/trekhleb/use-position" target="_blank" rel="nofollow noopener">usePosition() hook on GitHub</a>. I hope this example was informative for you. Happy coding!</p></div>