Syncing React State and Session Storage

I was recently working on code where an item of state of the app was being kept in sessionStorage rather than in React state. This was due to the fact that the state needed to be initialised in a different app flow from where it would be later used. Whenever the value of the state needed to be updated the update was made in sessionStorage and then on the next render of a component which required the state the value would be read from sessionStorage.

This felt a bit odd to me as sessionStorage was now the source of truth for this piece of app state. A component dependent on this state would only render with this new state value if it had another cause to re-render, not if the value in sessionStorage was updated.

If it is necessary to have state replicated in sessionStorage (or localStorage) a nicer pattern would be to keep the source of truth in React state and sync this to sessionStorage so if a new component which requires the state mounts it can pull in the value from sessionStorage to initialise its own state.

To do this we can utilise useEffect with a dependency on the React state we wish to persist to storage. This will result in the state value being written to storage whenever it is updated.

const STATE_KEY = "value";

sessionStorage.setItem(STATE_KEY, 0);

function MyComponent() {
  const [value, setValue] = useState(
    parseInt(sessionStorage.getItem(STATE_KEY))
  );

  useEffect(() => {
    sessionStorage.setItem(STATE_KEY, value);
  }, [value]);

  return (
    <>
      <p>The value is {value}</p>
      <button onClick={() => setValue(value + 1)}>increment</button>
    </>
  );
}

A better pattern altogether if it is possible may be to completely avoid sessionStorage and utilise a React context to store this state high up the app’s component tree where it can remain available for the duration of the session.

The lesson I learned from this experience is that if you have to use the Web Storage API in a React app be careful to ensure your components correctly re-render when the state is updated by keeping React state the source of truth and only initialising from and syncing back to storage.