DEV Community

Cover image for Using Local Storage in React with Your Own Custom useLocalStorage Hook
Nick Scialli (he/him)
Nick Scialli (he/him)

Posted on • Originally published at typeofnan.dev

Using Local Storage in React with Your Own Custom useLocalStorage Hook

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.


If you enjoy this tutorial, please give it a 💓, 🦄, or 🔖 and consider:


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) => {};
Enter fullscreen mode Exit fullscreen mode

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

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

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;
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
shadowtime2000 profile image
shadowtime2000

Now time to implement something like this with IndexedDB

Collapse
 
burzumumbra profile image
Ronald Flores Sequeira

Nice post, I'll implement this on a Chrome extension, for settings state management.