How to persist state after a page refresh in React using local storage

What if the browser gets Refreshed? How do we keep the state persistent?

Sometimes it is necessary to keep the state of a React component persistent even after a browser refresh. A simple way to accomplish this without having to rely on any third party library, is to use the localStorage API together with useEffect hook.

Making state persistent

Let’s say we want to build a counter component which it’s state should persist after a page refresh. Starting out, our component would look something along these lines:

function Counter() {
  const [count, setCount] = React.useState(0)

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={() => setCount(c => c - 1)}>-</button>
    </div>
  )
}

First, we want to save the value of count to the local storage every time it changes. For that we’ll use useEffect hook.

function Counter() {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    localStorage.setItem("count", count)
  }, [count])

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={() => setCount(c => c - 1)}>-</button>
    </div>
  )
}

If you try to refresh you app right now, you’ll notice that the value of the counter is still not persistent. However, if you inspect the local storage, you’ll notice that count is being stored. Now we only need to pass the value of count to the counter component after it has been rendered for the first time. To do that, we’ll use the useEffect hook again.

function Counter() {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    const parsedCount = Number(localStorage.getItem("count") || 0)
    setCount(parsedCount)
  }, [])

  React.useEffect(() => {
    localStorage.setItem("count", count)
  }, [count])

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={() => setCount(c => c - 1)}>-</button>
    </div>
  )
}

Using the Number constructor is necessary, since an item retrieved from local storage comes as a string.

Notice how the useEffect hook that sets the value of count is declared first. Order is important here. React will execute the effects given to useEffect in the same order we declared them. Switching the order of useEffect hooks would result in the count value always being 0 after the first render.

Considerations about this solution

By using this solution, we’re actually creating shared mutable state between all instances of the Counter component. This might or might not be a problem depending on the specific situation. As a rule of thumb, if we want to reuse a component across an application, this is not a good approach. However, remember to always think things through based on the current situation. No rule of thumb is better than a well done analysis of the situation that is right in front of you.

For this example, we used the string count as key for our counter value. On a real world application, we may want to use a unique ID to make sure we are not using and/or overriding any value that is being stored at the local stored. A key such as count-mhvXdrZT4jP5T8vBxuvm75 would be appropriate.

Give it a try

Next time you’re faced with having to make a React component’s state persistent across a browser refresh, remember to:

  1. Analyze your current situation to check if using Local Storage together with useEffect hook is appropriate
  2. Get a unique key to store your component state in order to avoid collisions at the Local Storage
  3. Add the necessary useEffect hooks to update and restore your component’s state

Enjoyed this article?Subscribe to the newsletter.

Subscribe to get content about building applications with React, understanding how React works, and sharpening your development process.