Hooks to persist state in the query string or history.state in React
Tags: javascript, react, webdev, showdev
Published: 2019-09-25 19:21:37


**TLDR;**
- embrace the URL and put part of your state into the query string of the URL (?simple=true)
- automatically support bookmarking, back, forward & refresh actions in the browser by doing so
- `useQueryState` from [`use-location-state`](https://github.com/xiel/use-location-state)  helps to put state into the query string
- `useQueryState(itemName, defaultValue)` works like `useState()`, but persists the state in the query string

## Intro: Benefits of good URLs

URLs are a fundamental technology of the web since their definition in the mid-90s. Being able to deep-link from one document to another was such a great idea that mobile operating systems even copied the principle for their app platforms. Apple calls them _Universal Links_, and Google calls them _App Links_. While native apps only surface a URL from time to time via the share sheet, URLs are always visible and accessible in the browser. So websites should maintain useful URLs.

Good URLs enable users to keep a reference to web applications in a specific state or to share them with other people. While the path of the URL usually defines the page the user is on, eg. a search results page (**/search**), the **query string** is often used to encode a custom state of that page, eg. a search query  for "shoes" with a set of filters for colors and size (/search<b>?q=shoes&color=blue&color=black&size=44</b>). A user could now bookmark this URL to come back later or share it with a friend or click on one of the products to check it out in detail, and if they want to return to the results page, they could just use the back functionality to go back to the same search results and select another product.

## Challenges: Maintaining the URL is hard... so we rarely do it

While the benefits of good URLs are apparent to most people, many modern SPAs build with frameworks like React still struggle to provide good URLs, because updating the URL and query string is harder than updating a local state or the redux store. I've been guilty of this myself, and I think the leading cause for this was the lack of an easy-to-use API.

An important design goal was to enable independent components on the page to take advantage of the query string and `history.state`, without them needing to know about each other. So a component only concerned about a specific part of the state, for example, the size filter parameter (?...&size=44), could read and update that state without having to deal with any other information stored in the query string.

## Introducing: useQueryState()

I went ahead to create a simple, yet powerful hook for React that works like `useState()`, but persists state in the query string of the URL. All that you need to use it is to choose a parameter name and pass a default value. The API looks like this:

```js
const [currentValue, updateValueFunction] = useQueryState(paramName, defaultValue)
```

The default value will be returned as the current value, as long as the value was not updated and the query string does not include a value for that parameter yet. In case this syntax (array destructuring) is new to you, I recommend reading about it in the [React Docs](https://reactjs.org/docs/hooks-state.html#tip-what-do-square-brackets-mean).


```jsx
function Search() {
  const [queryString, setQueryString] = useQueryState("queryString", "");
  return (
    <label>
      What are you looking for?
      <input
        value={queryString}
        onChange={e => setQueryString(e.target.value)}
        placeholder="Shoes, Sunglasses, ..."
      />
    </label>
  );
}
```

[Demo](https://lk4cs.csb.app) | [Code in CodeSandbox](https://codesandbox.io/embed/simple-search-example-lk4cs)

When a user now types a search term "shoes" into the text field, the query string of the URL will be updated to `/?queryString=shoes`. And you can reload, or go another page and return and the state will be restored correctly.

[![Demo of use Query State](https://thepracticaldev.s3.amazonaws.com/i/shcy1jwp8b8pglfcrect.gif)](https://0nzex.csb.app)

You can of course also use multiple `useQueryState()` hooks in a single component (or in separate components). Each `useQueryState()` automatically merges its updates with the currently encoded state in the query string.

```jsx
const  [queryString, setQueryString] =  useQueryState("queryString",  "");
const  [colors, setColors] =  useQueryState("colors",  []);

const toggleColor = e => {
  const color = e.target.value;
  setColors(
    colors.includes(color)
      ? colors.filter(t => t !== color)
      : [...colors, color]
  );
};

return (
  <form>
    ...
    <Color
      name="red"
      active={colors.includes("red")}
      onChange={toggleColor}
    />
    <Color
      name="blue"
      active={colors.includes("blue")}
      onChange={toggleColor}
    />
    ...
  </form>
)
```

[Demo](https://0nzex.csb.app) | [Code in CodeSandbox](https://codesandbox.io/embed/search-with-colors-example-0nzex)

`useQueryState()` currently supports following value types:  `string | number | boolean | Date | string[]`.

The query string is a global state, so choose the parameter names wisely to prevent accidental clashes. But it is of course allowed to use the **same parameter name** on purpose when you want access to the **same state in multiple places**.

## If not safe for the URL: useLocationState()

In some cases, you might not want to store the state in the query string of the URL, but still, want the user to be able to restore a previous state using the back/forward actions of the browser. To enable this `useLocationState()` persists state in the [history state](https://developer.mozilla.org/en-US/docs/Web/API/History/state) instead.

The API works the same. You provide a name and a default value and get the current value, and the update function returned as a pair.

```js
const [currentValue, updateValueFunction] = useLocationState(paramName, defaultValue)
```

For persisting complex or more sensitive state `useLocationState()` is more suitable, for example, the state of a comment form. Also, state based on data that frequently changes is better suited to be stored in `history.state`. This way, you can avoid offering URLs that only work for a short time.

## Installation / Usage

You can install these hooks using yarn or npm:

`yarn add use-location-state`

Import the hooks where you want to use them:

`import { useLocationState, useQueryState } from 'use-location-state'`

### Are you using  **`react-router`**  or another popular router?

For the best experience install one of the  [router integrations](https://github.com/xiel/use-location-state#router-integration-optional).

`yarn add react-router-use-location-state`

And use these imports:

`import { useLocationState, useQueryState } from 'react-router-use-location-state'`


## Thanks!

I hope you find this introduction and the library is useful! Happy to discuss enhancements and answer questions 👏