How and why you should store React UI state in the URL
Tags: javascript, react, webdev, tutorial
Published: 2021-12-10 02:50:19

### Deep linking in React, as simple as useState

Have you ever used a complex web app with many features, modal windows, or side panels? Where you get to the perfect state with just the right information on the screen after a few clicks through different screens, but then you accidentally close the tab? (Or Windows decides to update?)

It would be great if there were a way to return to this state without going through the same tedious process. Or be able to share that state so a teammate can work on the same thing you are.

This problem could be solved with deep linking, which is used today in mobile apps to open the app to a specific page or UI state. But why does this not exist in many web apps?

⏭ [Click here to skip to the solution and code snippets.](#heading-the-code) 

## Bring back deep linking on the web

The emergence of single-page applications (SPAs) has allowed us to craft new user experiences that are instantly interactive on the web. By doing more on the client side using JavaScript, we can respond to user events immediately, from opening custom dialog windows to live text editors like Google Docs.

Traditional server-rendered websites send a request to get a new HTML page every single time. An excellent example is Google, which sends a request to its servers with the user’s search query in the URL: [https://www.google.com/search?**q=your+query+here**](https://www.google.com/search?q=your+query+here). What’s great about this model is that if I filter by results from the past week, I can share the same search query by simply sharing the URL: [https://www.google.com/search?q=react+js&**tbs=qdr:w**](https://www.google.com/search?q=react+js&tbs=qdr:w). And this paradigm is entirely natural for web users—sharing links has been part of the world wide web ever since it was invented!

![Annotated screenshot of a Google search page. The search term input is highlighted and an arrow points to the corresponding part in the URL that stores the search term. The results are filtered to only show those from the past week, and another arrow points to the corresponding part in the URL that stores this data.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x20gr9ovgelp6o8avmbu.png)

When SPAs came along, we didn’t need to store this data in the URL since we no longer need to make a server request to change what is displayed on the screen (hence *single-page*). But this made it easy to lose a unique experience of the web, the shareable link.

Desktop and mobile apps never really had a standardized way to link to specific parts of the app, and modern implementations of deep linking rely on URLs on the web. So when we build web apps that function more like native apps, why would we throw away the deep linking functionality of URLs that we’ve had for decades?

## Dead-simple deep linking

When building a web app that has multiple pages, the minimum you should do is change the URL when a different page is displayed, such as `/login` and `/home`. In the React ecosystem, [React Router](https://reactrouter.com/) is perfect for client-side routing like this, and [Next.js](https://nextjs.org/) is an excellent fully-featured React framework that also supports server-side rendering.

But I’m talking about *deep* linking, right down to the UI state after a few clicks and keyboard inputs. This is a killer feature for productivity-focused web apps, as it allows users to return right to the exact spot they were at even after closing the app or sharing it with someone else so they can start work without any friction.

<figure>

![Screen recording of a modal window being opened, causing the URL to update to add `#modal="webhooks"`, which is the internal state that triggers the modal to open.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3o4grhdkx1g2dzw1zatb.gif)

<figcaption>

Notice how the URL updates to add `#modal="webhooks"` as the modal opens.

</figcaption>
</figure>

You could use npm packages like [query-string](https://www.npmjs.com/package/query-string) and write a basic React Hook to sync URL query parameters to your state, and there are [plenty](https://medium.com/swlh/using-react-hooks-to-sync-your-component-state-with-the-url-query-string-81ccdfcb174f) of [tutorials](https://www.npmjs.com/package/use-query-params) for [this](https://dev.to/gaels/an-alternative-to-handle-global-state-in-react-the-url--3753), but there’s a more straightforward solution.

While exploring modern state management libraries for React for an architecture rewrite of our React app [Rowy](https://rowy.io/?utm_source=dev.to&utm_medium=blog&utm_campaign=How%20and%20why%20you%20should%20store%20React%20UI%20state%20in%20the%20URL), I came across [Jotai](https://jotai.org/), a tiny atom-based state library inspired by the React team’s [Recoil](https://recoiljs.org/) library.

The main benefit of this model is that state atoms are declared independent from the component hierarchy and can be manipulated from anywhere in the app. This solves the issue with React Context causing unnecessary re-renders, which I [previously worked around with `useRef`](https://betterprogramming.pub/how-to-useref-to-fix-react-performance-issues-4d92a8120c09). You can read more about the atomic state concept in [Jotai’s docs](https://jotai.org/docs/basics/concepts) and a more technical version in [Recoil’s](https://recoiljs.org/docs/introduction/motivation).

## The code

Jotai has a type of atom called [`atomWithHash`](https://jotai.org/docs/api/utils#atom-with-hash), which syncs the state atom to the URL hash.

Suppose we want a modal’s open state stored in the URL. Let’s start by creating an atom:

{% gist https://gist.github.com/notsidney/d0caa489ee77da35d9af19ec085ac4d2 file=atoms.ts %}

Then in the modal component itself, we can use this atom just like `useState`:

{% gist https://gist.github.com/notsidney/d0caa489ee77da35d9af19ec085ac4d2 file=ExampleModal.tsx %}

And here’s how it looks:

![Screen recording of a modal being opened, causing the URL to update to reflect the UI state, with #modalOpen=true being appended. When the modal is closed, it is replaced with modalOpen=false.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gq08ssz4b61wl2pu4n2g.gif)

And that’s it! It’s that simple.

What’s fantastic about Jotai’s `atomWithHash` is that it can store any data that `useState` can, and it automatically stringifies objects to be stored in the URL. So I can store a more complex state in the URL, making it sharable.

In [Rowy](https://rowy.io/?utm_source=dev.to&utm_medium=blog&utm_campaign=How%20and%20why%20you%20should%20store%20React%20UI%20state%20in%20the%20URL), we used this technique to implement a UI for cloud logs. We’re building an open-source platform that makes backend development easier and eliminates friction for common workflows. So, reducing friction for sharing logs was perfect for us. You can see this in action on our demo, where I can link you to a specific deploy log: [https://demo.rowy.io/table/roadmap#modal="cloudLogs"&cloudLogFilters={"type"%3A"build"%2C"timeRange"%3A{"type"%3A"days"%2C"value"%3A7}%2C"buildLogExpanded"%3A1}](https://demo.rowy.io/table/roadmap#modal=%22cloudLogs%22&cloudLogFilters=%7B%22type%22%3A%22build%22%2C%22timeRange%22%3A%7B%22type%22%3A%22days%22%2C%22value%22%3A7%7D%2C%22buildLogExpanded%22%3A1%7D)

![Screen recording of the deep link opening the Rowy demo web app to the cloud logs modal being open.](https://cdn.hashnode.com/res/hashnode/image/upload/v1638925723546/qT9qAZ0YZ.gif)

Decoding the URL component reveals the exact state used in React:

{% gist https://gist.github.com/notsidney/d0caa489ee77da35d9af19ec085ac4d2 file=cloudLogState.ts %}

A side effect of `atomWithHash` is that it pushes the state to the browser history by default, so the user can click the back and forward buttons to go between UI states.

![Screen recording of the user clicking the back button in the browser repeatedly, causing the UI state to change with modals being opened and closed.](https://cdn.hashnode.com/res/hashnode/image/upload/v1638925771468/ZyUEGiWcn.gif)



This behavior is optional and can be disabled using the [`replaceState` option](https://jotai.org/docs/api/utils#atom-with-hash):



{% gist https://gist.github.com/notsidney/d0caa489ee77da35d9af19ec085ac4d2 file=atomReplaceState.ts %}

---

Thanks for reading! I hope this has convinced you to expose more of your UI state in the URL, making it easily shareable and reducing friction for your users—especially since it’s effortless to implement.

You can follow me on Twitter [@nots_dney](https://twitter.com/nots_dney) for more articles and Tweet threads about front-end engineering.

{% github rowyio/rowy %}