TypeOfNaN

Using Local Storage in React with Your Own Custom useLocalStorage Hook

Nick Scialli
September 01, 2020

man catching boot

One tool for storing data browser-side we might reach for is local storage. In this post, we’ll use local storage in React by rolling our own useLocalStorage hook.

Update

I have created an npm module, uselocalstorage, based on this increasingly popular blog post! Please check it out, let me know what you think, and feel free to contribute to it!

npm link: use-local-storage

Installing with npm:

npm install use-local-storage

Installing with yarn:

yarn add use-local-storage

Our Approach

To approach this problem, let’s break it down into pieces.

  1. Provide a local storage key. Local storage works off of key-value pairs, so we’ll want to be able to provide a key for our stored data.
  2. Provide a default value. If there’s no existing data in local storage under the provided key, we’ll want to be able to provide a defualtValue for our data.
  3. Load the local storage value into state (or default if no local storage value exists). We’ll still be maintaining stateful information in our app, so we can still use the useState hook. The difference here is we’ll use the local storage value if it exists before we consider the user-provided defaultValue.
  4. Save the stateful data to local storage. When our stateful data changes, we’ll want to make sure local storage is kept up to date. Therefore, on any change to our variable, let’s run an effect to sync up local storage.
  5. Expose the state variable and a setter. Much like the useState hook, our useLocalStorage hook will return a 2-element array. The first element will be the variable and the second will be a setter for that variable.

Creating the Hook

Let’s create the hook! As noted above, the hook will take two inputs: the key that will be used in localStorage and the defaultValue, which will be used in the even that there’s nothing in localStorage yet.

useLocalStorage.js

export const useLocalStorage = (key, defaultValue) => {};

Next up, let’s load any data in localStorage under the provided key.

export const useLocalStorage = (key, defaultValue) => {
  const stored = localStorage.getItem(key);
};

Now we know that the initial value for our stateful variable is going to be this stored value. However, if there’s nothing in localStorage yet under the provided key, we’ll default to the user-provided defaultValue.

Note: since localStorage data are stored as strings, we make sure to JSON.parse any data we retrieve from there.

export const useLocalStorage = (key, defaultValue) => {
  const stored = localStorage.getItem(key);
  const initial = stored ? JSON.parse(stored) : defaultValue;
};

Now that we have our initial value for state, we can use our regular useState hook format and return our stateful variable and its setter.

import { useState } from 'react';

export const useLocalStorage = (key, defaultValue) => {
  const stored = localStorage.getItem(key);
  const initial = stored ? JSON.parse(stored) : defaultValue;
  const [value, setValue] = useState(initial);
  return [value, setValue];
};

Almost done! We still have one outstanding requirement we haven’t met yet: we need to save any data back to localStorage when it’s changed. I like doing this in a useEffect hook that’s triggered when value changes.

import { useState, useEffect } from 'react';

export const useLocalStorage = (key, defaultValue) => {
  const stored = localStorage.getItem(key);
  const initial = stored ? JSON.parse(stored) : defaultValue;
  const [value, setValue] = useState(initial);

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

  return [value, setValue];
};

There we have it! Whenever value changes, our effect will run, meaning we’ll set the localStorage item to be set to the JSON.stringify of our value. Note that the provided key is also a dependency of our effect, so we include it in the dependency array for completeness even though we don’t really expect it to change.

Testing Out Our New Hook

Let’s take the hook our for a test drive! We’ll create a simple component that has a text input whose value is based on our useLocalStorage hook.

App.jsx

import React from 'react';
import { useLocalStorage } from './useLocalStorage';

function App() {
  const [name, setName] = useLocalStorage('username', 'John');
  return (
    <input
      value={name}
      onChange={(e) => {
        setName(e.target.value);
      }}
    />
  );
}

export default App;

Now let’s run our app. We can see that, when we first run the app, our stateful name variable is defaulted to the string John. However, when we change the value and then refresh the page, we’re now defaulting to the value persisted to localStorage.

useLocalStorage in use

Success!

Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli