Persisting state in React that survives closing the browser tab

Jonas Neumann
NEW IT Engineering
Published in
4 min readMar 26, 2021

--

Yes, that title is loaded. So, before I dive in deeper, please beware that you should have a decent understanding of React.js to be able to follow along.

I’m probably not the only one that experienced instant regret when closing a browser tab. I just took 10 minutes to apply the right filters and sorting to show exactly what I want to see and now it’s gone!

If you want to prevent your users from having to go through this, please read on!

In technical terms, if you are looking for a solution to persist some local state in your React app, then this is for you. Granted, there are other ways to ensure you can go back to a previous state of a website, like working with very granular routes, but here I’m going to show you how you can leverage the browsers local storage and the React Context API to cache state that persists even when you close your browser.

Let’s dive in! First, let’s look at the different parts that make this work and then look at each of them individually. The parts are:

1. The Local Storage API,

2. The React Context API,

3. Writing a Higher Order Component to use Local Storage with the Context API in your app

Local Storage API

There are many ways to locally store state in your app. I won’t cover all alternatives, but rather only focus on why you might want to use Local Storage and how you access the API.

If you are interested in the different ways to store local (client side) state in your app, take a look at cookies, session storage and IndexedDB.

The Local Storage or more specifically the localStorage attribute of the Web Storage API provides a way to store key-value pairs. The advantages of using Local Storage instead of cookies, session storage or IndexedDB is that it’s client-side only (the server has no access), it persists even after closing the browser and its API is simpler than that of IndexedDB.

To store a key-value pair you simply call the following API:

window.localStorage.setItem(<KEY>, <VALUE>);

To retrieve data from Local Storage, you need to know the key. You can retrieve the value by providing the key:

window.localStorage.getItem(<KEY>);

To get around the limit of only being able to store Strings, we can simply use parsing:

window.localStorage.setItem(<KEY>, JSON.stringify(<VALUE>));

JSON.parse(window.localStorage.getItem(<KEY>));

One more recommendation would be to treat the Local Storage API the same way you would treat an external API. Although most browsers support this API, there is no guarantee that your users’ browsers will. Just implement error handling, logging, fallbacks etc. like you would for an external REST API.

React Context API

This part is actually optional. I’d only recommend using the React Context API, if you need the data you persist in Local Storage in multiple locations in your app and passing props would create massive overhead.

That being said, the useContext hook is a neat way to get around overloading your React Components with props. But as mentioned, beware the useContext hook introduces a “side-effect” that makes testing and reusing the Component more difficult. (Find more in the docs: https://reactjs.org/docs/context.html)

Let’s look at some code!

We need to implement 3 things.

1. The Context Component

2. A Context Provider (Wrapper to inject the Context into the Component tree)

3. A Context Consumer (Logic to access the Context from the Context Provider)

The Context itself is very simple. You simply call the createContext method and optionally provide a default value:

const MyContext = React.createContext(defaultValue);

Using the Context Component, you can now use the Context Provider prop with the key “value” to provide the value you pass to all Context Consumers

<MyContext.Provider value={/* some value */}></MyContext.Provider>

As the Context Consumer we will use the React hook useContext.

const value = useContext(MyContext);

This doesn’t look very useful, if we stopped here. To make the Context more useful we can create a Higher Order Component for the context provider to pass down additional functionality, e.g. not just the value, but also a function to update the value:

The Context Consumer now has access to the value and the set value function. Now any Component that implements a Context Consumer can update global state like this:

Writing a Higher Order Component (HOC)

Let’s combine both approaches!

To combine both approaches we need to rewrite our “MyProvider” HOC. Currently our “MyProvider” Component stores all state using the React useState hook. To keep it simple, let’s write a custom hook that builds upon the useState hook. The custom hook will first read from local storage and provide the value to state and then write state updates to local storage.

Now all we need to do is change the hook we use in our MyProvider Component from useState to useLocalStorage:

With these few lines of code we have created a way to read and write state from anywhere in our app and persist that state in local storage.

--

--

Jonas Neumann
NEW IT Engineering

Cloud Architect and Full-Stack Developer at Accenture