How To Use URLs to Store State in a React App

How to customize the hook to reuse it?

Zachary Lee
JavaScript in Plain English

--

Photo by Memento Media on Unsplash

React provides a simple and clear way to manage the state of WebApp, however, when it comes to sharing or bookmarking the current state of a React app, there is no built-in mechanism to achieve this. In this article, we will explore how React apps can store state on URLs and create shareable and bookmarkable links.

Why store state on URLs?

Storing state data on URLs has several benefits:

  1. Shareable links — URLs are easy to share with others via email, chat, or social media.
  2. Bookmarkable links — URLs can be bookmarked for future reference.
  3. Search engine optimization — URLs with meaningful and structured state data can improve search engine ranking and user experience.
  4. Back/forward navigation — URLs with state data can be used to navigate back and forward within an application, providing a better user experience.

Storing state on URLs using query parameters

One way to store state data on a URL is to use query parameters. Query parameters are key-value pairs that are appended to the end of a URL, separated by a question mark (?). Each key-value pair is separated by an ampersand (&). For example:

https://example.com/item?name=desktop&height=30

In React, we can use the useEffect hook to update the query parameters when the state changes. The useEffect hook is called after the component is rendered and whenever the dependencies change. Here is a simple example:

import React, { useState, useEffect } from 'react';

function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);

useEffect(() => {
const searchParams = new URLSearchParams(window.location.search);
searchParams.set('count', String(count));
window.history.replaceState(null, '', `?${searchParams.toString()}`);
}, [count]);

return (
<div>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}

export default Counter;

When the user clicks on the “Increment” or “Decrement” button, the count state is updated, and the useEffect hook is called, which updates the query parameters in the URL. The URL will now look like this:

https://example.com/counter?count=1

But this isn’t done yet, if you bookmark or share this URL, we expect them to see the current count status when they open the link. So we need to extract the count query parameter at initialization time:

import React, { useState, useEffect } from 'react';

function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);

useEffect(() => {
const searchParams = new URLSearchParams(window.location.search);
const countParam = searchParams.get('count');
if (countParam !== null) {
setCount(Number(countParam));
}
}, []);

useEffect(() => {
const searchParams = new URLSearchParams(window.location.search);
searchParams.set('count', String(count));
window.history.replaceState(null, '', `?${searchParams.toString()}`);
}, [count]);

return (
<div>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}

export default Counter;

In the first useEffect hook, we check if the “count” query parameter is present in the URL and set the count state accordingly. The second useEffect hook updates the query parameters whenever the count state changes.

Storing state on URLs using the hash fragment

Another way to store state data on a URL is to use the hash fragment. The hash fragment is a part of the URL that comes after the “#” symbol. It is often used to create anchor links within a page. For example, the following URL has a hash fragment that points to an anchor element with the id “section-2”.

https://example.com/page#section-2

Use it in React:

import { useState, useEffect } from 'react';

function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);

useEffect(() => {
const hashParams = new URLSearchParams(window.location.hash.slice(1));
const countParam = hashParams.get('count');
if (countParam !== null) {
setCount(Number(countParam));
}
}, []);

useEffect(() => {
window.location.hash = `count=${count}`;
}, [count]);

return (
<div>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}

export default Counter;

It can be seen that there is not much difference from the examples we mentioned above, but some API replacements.

Using the hash fragment to store state data on a URL has a few advantages over using the search query parameters:

  1. The hash fragment can be used to store arbitrary data without affecting the behavior of the browser or the server. In contrast, modifying the search query parameters can have unintended side effects, such as triggering a server-side search(Non-SPA) or breaking a cached URL.
  2. The hash fragment can be used to create deep links to specific views or states within an application. This is useful for bookmarking or sharing a specific state of an application.

Use with React-Router

Normally, React App will be used with React-Router, so how do I use the state on the Url in this case? Here’s a simple example I gave:

Here is a use case:

For pedagogical purposes, it’s very simplified, setState doesn't do any optimizations, and it doesn't support many options. If you want to find a hook to use in a production environment, you can try ahooksjs/use-url-state.

In fact, React-Router also uses history or hash API internally. Here is an article for reference:

Conclusion

Storing state data on URL is necessary for some cases, I hope this article is helpful to you.

Thanks for reading. If you like such stories and want to support me, please consider becoming a Medium member. It costs $5 per month and gives unlimited access to Medium content. I’ll get a little commission if you sign up via my link.

Your support is very important to me — thank you.

More content at PlainEnglish.io.

Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Interested in scaling your software startup? Check out Circuit.

--

--