Using URL as a global state - React Hook
Tags: react, javascript, webdev
Published: 2020-04-07 02:51:31

Sometimes in small applications using some state manager like Redux can be a little pain in the *, with this post I hope to help with a less spartan way to achieve this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tkalftqv79ldlfhlarpb.png)

For this litle POC we will use ReactJS, NextJS and [Dog Ceo Api](https://dog.ceo/dog-api/)

The main purpose of this solution is to avoid creating a state in a parent component and keep passing it and its setter as props to the children components.

In this example we have two components: a `Home` in `pages/index/index.js` and some buttons in `pages/components/breed-buttons`. 
*You can checkout full code at my [github page](https://github.com/Viglioni/global-state-example) and see it running [here](https://global-state-example.herokuapp.com/) :)*

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3xge8bhecd373crhupv8.png)

Our `Home` component have a state called *breed* with "random" as its default value and this component makes an API call to get a random picture of a dog. Usually we would do something like:

```
const Home = () => {
    const [breed, setBreed] = useState("random")
    /* api call */
    /* display pic */
```

Our `BreedButtons` component is a simple `div` with some buttons with breed names that when clicked set our breed state with its respective breed value. For this to be possible, we must pass `breed` and `setBreed` as props:

```
const Home = () => {
    const [breed, setBreed] = useState("random")
    /* api call */
    <BreedButtons breed={breed} setBreed={setBreed}/>
    /* display pic */
```
Now just *picture* the scene when `Home` has a lot of children that might read or write this state. And you have more states. It might get messy.

In our `helpers/hooks.js` you will find this React Hook:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9l8k0o8mt3mdcsx1ygrg.png)

*(obs.: if you do not know `pathOr` you should checkout [ramda](https://ramdajs.com/docs/#pathOr), it is an amazing functional lib!)*

This function gets two parameters: first the name of this state and second the initial value. For instance, in our problem we want a state called *breed* with default value *random*. The use is very similar to React's `useState`:

```
const [breed, setBreed] = useRouterAsState("breed", "random")
```

Our hook will check if our URL has already some value for the state "breed", for instance `https://global-state-example.herokuapp.com/?breed=husky`, if yes it will set the state to "husky" or whatever it is placed after the equal sign, if not to our default value "random".

Whenever we change the state in any component, *i.e.*
```
setBreed("dalmatian")
```
the `next router` will change in URL to `/?breed=dalmatian` and **all** components the used our `useRouteAsState` will automatically update its value. If we have more states in our URL it will change only the "breed" state. That is why you name the state in `useRouteAsState` first parameter.



This is what our `Home` looks like:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6nqlquoibu6wu4j9akih.png)

where `getDog` is a syntax sugar for our API call in `helpers/api.js`. Our `BreedButtons` component contains the buttons that actually change our state and it looks like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/iv4njfbyijthxxyik0vg.png)



Of course our approach has **a lot** of limitations such as: it simply does not make sense for some states to be in URL like *loading* or *data*, but it is very usefull to states like pagination, dark-mode etc. 
Be careful, it might get messy if more than one component tries to change the state at the same time, so **you must use it very carefully when thinking about [concurrency](https://en.wikipedia.org/wiki/Concurrency_(computer_science))**. 

On the other hand, more than just being *handy* not to have to pass the same state and setter to a lot of children, grandchildren again and again, the state in URL has a **huge** advantage of not losing context when the page is reloaded or when you hit back button - in mobile web development this behaviour is **fundamental**.


Another important point: sometimes you don't want to give the power of manually setting a state to the user just typing in the URL, specially if some of the states are being used in an API call. For this problem we have a partial solution.

If you check our `pages/encoded/` code you will see that the components are very similar to our `pages/index` ones, except that our hook is imported from `helpers/encoded-state.js`. The code is a little bit longer so I won't print it here, but the main difference is instead of our URL be something like `?breed=labrador` it will be `?c3RhdGVz=eyJicmVlZCI6ImxhYnJhZG9yIn0%253D`. It is a Base64 encoding of 

```
state: {
    breed: "labrador"
}
```
The user will be able to decode it, but it is a little bit safer than just let the state in plain text.


A last but not least point: you might have noticed that in our `useRouteAsState` we have a third parameter called `r`. In some older versions of `NextJS` the `useRouter` native hook does not work, so you must import `withRouter` from `next/router` and wrap your component using it, for example: `export default withRouter(Home)` with that you will receive a prop called `router` in `Home` component that is our third paramter.


I really hope you find this little trick useful. Any doubts or sugestions you can call me on my twitter account [twitter.com/viglionilaura](https://twitter.com/ViglioniLaura) :)