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?
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. 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. And this paradigm is entirely natural for web users—sharing links has been part of the world wide web ever since it was invented!
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 is perfect for client-side routing like this, and Next.js 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.
You could use npm packages like query-string and write a basic React Hook to sync URL query parameters to your state, and there are plenty of tutorials for this, but there’s a more straightforward solution.
While exploring modern state management libraries for React for an architecture rewrite of our React app Rowy, I came across Jotai, a tiny atom-based state library inspired by the React team’s Recoil 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. You can read more about the atomic state concept in Jotai’s docs and a more technical version in Recoil’s.
The code
Jotai has a type of atom called atomWithHash, 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:
Then in the modal component itself, we can use this atom just like useState:
And here’s how it looks:
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.
Decoding the URL component reveals the exact state used in React:
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.
This behavior is optional and can be disabled using the replaceState option:
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 for more articles and Tweet threads about front-end engineering.
Low-code backend platform. Manage database on spreadsheet-like UI and build cloud functions workflows in JS/TS, all in your browser.
✨ Airtable-like UI for managing database ✨ Build any automation, with or without code ✨
Connect to your database and create Cloud Functions in low-code - without leaving your browser.
Focus on building your apps
Low-code for Firebase and Google Cloud