TypeOfNaN

Using Session Storage in React with Hooks

Nick Scialli
March 28, 2021

In React, one great way to persist user data in session storage is by using Hooks. Here’s how to do it.

The Setup

Let’s say we have an enterprise application and we need our users to accept terms of service somewhat frequently. As a result of analyzing different options, the team has determined that session storage is the right place to store whether or not the user has accepted the terms.

Our React app will, therefore, need to read session storage to determine if the user has accepted the terms. If they have, they get to see our application. If they haven’t accepted the terms yet, they see the Terms of Service and a button to accept them. Once they accept the terms, this acceptance is stored in session storage so we don’t have to ask the user again for a bit.

With Using Session Storage

We can code up a bare bones example without session storage. This just uses the useState hook to persist the users decision for the entire time our App is mounted. This works pretty well, but if we refresh the page, the user is prompted to accept the terms again!

import { useState } from 'react';

function App() {
  const [termsAccepted, setTermsAccepted] = useState(false);
  if (!termsAccepted) {
    return (
      <>
        <h1>Terms of Service</h1>
        <p>These are the terms for using the application.</p>
        <button
          onClick={() => {
            setTermsAccepted(true);
          }}
        >
          I accept
        </button>
      </>
    );
  }

  return <div>This is the application</div>;
}

We can see this in action here:

accepting terms

This actually ends up getting us a large part of the way there! Let’s see how we can add in session storage.

Adding in Session Storage

There are two considerations when adding session storage to the equation:

  1. How to load data from session storage
  2. How to save data to session storage

Loading from Session Storage

We can actually load from session storage pretty much how we would do so in JavaScript (without React) by using sessionStorage.getItem. Importantly, we’re going to want to make sure that we use a default value if there’s nothing in session storage.

Additionally, we know that session storage requires values to be strings. Therefore, we’re going to assume that anything we’ve put in session storage is stringified as JSON.

We can therefore create the following function, which will get an item from session storage if it exists, or return a default value.

function getSessionStorageOrDefault(key, defaultValue) {
  const stored = sessionStorage.getItem(key);
  if (!stored) {
    return defaultValue;
  }
  return JSON.parse(stored);
}

We can implement this in our React app as follows. Note we’re using the session storage key "terms" for our terms of service.

import { useState } from 'react';

function getSessionStorageOrDefault(key, defaultValue) {
  const stored = sessionStorage.getItem(key);
  if (!stored) {
    return defaultValue;
  }
  return JSON.parse(stored);
}

function App() {
  const [termsAccepted, setTermsAccepted] = useState(
    getSessionStorageOrDefault('terms', false)
  );

  if (!termsAccepted) {
    return (
      <>
        <h1>Terms of Service</h1>
        <p>These are the terms for using the application.</p>
        <button
          onClick={() => {
            setTermsAccepted(true);
          }}
        >
          I accept
        </button>
      </>
    );
  }

  return <div>This is the application</div>;
}

Okay, great, but we also need to save to session storage or else there’s no point in loading from session storage. Let’s tackle the saving part.

Saving to Session Storage

In my experience, the most idiomatic way to save to storage in React when a stateful variable is updated is to use an effect. The idea is that the effect will run every time the stateful variable is updated, ensuring that the most recent data is saved to storage.

Our useEffect hook in isolation might look like this:

useEffect(() => {
  sessionStorage.setItem('terms', JSON.stringify(termsAccepted));
}, [termsAccepted]);

And this will achieve the effect we’re going for!

All Together

All together, our app would now look like this:

import { useState, useEffect } from 'react';

function getSessionStorageOrDefault(key, defaultValue) {
  const stored = sessionStorage.getItem(key);
  if (!stored) {
    return defaultValue;
  }
  return JSON.parse(stored);
}

function App() {
  const [termsAccepted, setTermsAccepted] = useState(
    getSessionStorageOrDefault('terms', false)
  );

  useEffect(() => {
    sessionStorage.setItem('terms', JSON.stringify(termsAccepted));
  }, [termsAccepted]);

  if (!termsAccepted) {
    return (
      <>
        <h1>Terms of Service</h1>
        <p>These are the terms for using the application.</p>
        <button
          onClick={() => {
            setTermsAccepted(true);
          }}
        >
          I accept
        </button>
      </>
    );
  }

  return <div>This is the application</div>;
}

And if we test this out in our browser, we see that if we refresh the page after we accept the terms, our app remembers that we have already accepted the terms.

Success!

Creating a useSessionStorage Hook

If we need to use session storage multiple times in the app, it would be tedious to repeat all this logic. Instead, we can create a useSessionStorage hook to do it for us!

The signature of our hook will take the name of the key and the default value in case there is nothng stored yet. The hook will return the same things that a useState hook returns—the current value and a setter function.

useSessionStorage.js

import { useState, useEffect } from 'react';

function getSessionStorageOrDefault(key, defaultValue) {
  const stored = sessionStorage.getItem(key);
  if (!stored) {
    return defaultValue;
  }
  return JSON.parse(stored);
}

export function useSessionStorage(key, defaultValue) {
  const [value, setValue] = useState(
    getSessionStorageOrDefault(key, defaultValue)
  );

  useEffect(() => {
    sessionStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

If we want to use this custom hook in our App file, we can do so as follows. Notice how simple it becomes; it’s basically as if we’re using the useState hook with the exception that we have to provide the session storage key.

import { useSessionStorage } from './useSessionStorage';

function App() {
  const [termsAccepted, setTermsAccepted] = useSessionStorage('terms', false);

  if (!termsAccepted) {
    return (
      <>
        <h1>Terms of Service</h1>
        <p>These are the terms for using the application.</p>
        <button
          onClick={() => {
            setTermsAccepted(true);
          }}
        >
          I accept
        </button>
      </>
    );
  }

  return <div>This is the application</div>;
}

Conclusion

It’s fairly trivial to use session storage in a React app using hooks. Furthermore, it’s easy to extract this logic out into its own hook, making our App code super clean.

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli