DEV Community

Cover image for HowTo: Custom React localStorage Hook ⚓
sanderdebr
sanderdebr

Posted on

HowTo: Custom React localStorage Hook ⚓

Traditionally React had two popular ways to share stateful logic between components: render props and higher-order components. Hooks solve many of the problems these two techniques have.

In this tutorial you will learn how to create your own Hooks in React by building a custom Hook for storing and reading the Local Storage web API! 😃

➡️ Checkout what we're building.


What is a custom hook?

First, let's check what a custom hook actually is. According to the official React docs, a custom Hook is a JavaScript function whose name starts with use and that may call other Hooks. Hooks are functions that let you hook into React state and lifecycle from function components). Sounds a bit vague still right. Next up, let's see what localStorage is.


Local Storage

The read-only localStorage property of the global window object gives us the possibility to store data at the client side without an expiration time (sessionStorage contrarily gets lost after closing the browser).

So.. what can you actually do with localStorage? Many things! Like:

  • Remembering last search term
  • Save your comment
  • Save the username
  • Toggle theme
  • Many more..

Keep in mind though:

  • Do not store sensitive user information in localStorage
  • Limited to 5MB across all major browsers
  • No form of data protection (do not store e.g. JWT tokens here)

Let's start with building our Hook!

Setting an item with localStorage is very easy. Let's set our theme to dark:

localStorage.setItem('theme', 'dark');

And reading is easy as well:

localStorage.getItem('theme');

Awesome right!

Now, what we actually want to achieve here is to use the localStorage methods from everywhere in our React application.

Let's create a new folder called /hooks and create a function called useLocalStorage that acceps two arguments: a key and an initialValue.

export const useLocalStorage = (key, initialValue) => {
  ...
}

export default useLocalStorage;

When we are finished with our Hook, we want to use it as follows:

// Destructuring off an array
const [storedTheme, setTheme] = useLocalStorage("theme");
// Get the current theme
const theme = storedTheme;
// Setting a theme
setTheme("dark");

This means our Hook has to return an array containing:

  • a value with the requested stored item in localStorage and
  • a function that can set an item in localStorage.

We'll use the useState Hook to let React keep track of the stored value.

import { useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(initialValue);

  const setValue = (value) => {
    window.localStorage.setItem(key, value);
    setStoredValue(value);
  }

  return [storedValue, setValue];
}

We only have set up the basis but let's test it already! 🤖

Inside another component, import our Hook and add:

import React, { useEffect } from "react";
import { useLocalStorage } from "./hooks/useLocalStorage";

function App() {
  const [storedTheme, setTheme] = useLocalStorage("theme");

  useEffect(() => {
    setTheme("dark");
  }, [setTheme]);

  console.log(storedTheme);

  return <h1>Hi Dev</h1>
}

You should see 'dark' in your console and also you can see the localStorage items with Chrome Dev Tools:

localStorage

We're not ready yet, let's improve our Hook!


Improving our Hook

We want to improve our hook so we can do the following:

  • Setting a default value
  • Store objects and functions
  • Add error handling if our function failes

To get our stored value, we will add a function to our useState Hook that checks if the item exists inside our localStorage. If not, we'll throw an exception with a try-catch block and return the initialValue so the state always stores our theme if localStorage failes.

We'll use JSON.parse() to transform an object to a string so we can also store objects.

...
export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (err) {
      console.warn("Setting localStorage went wrong: ", err);
      return initialValue;
    }
  });
...
};

Awesome! 👍

To test this we can create use our hook to create a new storage item to store a name. Without setting it in localStorage yet, we can let useState store our name:

const [storedName, setUsername] = useLocalStorage("name", "Tim");
console.log(storedName); // Gives Tim without using localStorage

Finally, we'll add a try-catch block to our setValue function, add JSON.stringify() to transform our string back to an object and check if the stored value is a function:

...
const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
...

Our complete Hook:

import { useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (err) {
      console.error(err);
      return initialValue;
    }
  });

  const setValue = value => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (err) {
      console.error(err);
    }
  };

  return [storedValue, setValue];
};

export default useLocalStorage;

That's it! We now have a custom hook that we can use anywhere in our application and store any variable we'll like.

Thanks for following this tutorial.

Make sure to follow me for more tips and tricks. 🤓

Top comments (4)

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

I would just say that your hook doesn't actually update when two components both set the value independently. An event system to ensure that all hooks using "theme" or whatever changed at once would make this much more powerful.

I don't mean to be overly negative, it's really nice :)

Collapse
 
sanderdebr profile image
sanderdebr • Edited

True thanks for the tip! Hooking this up to, for example, a useReducer() hook would ensure a global state like that, but I thought it would be outside of this tutorial.

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Ah I was thinking of adding and event handler for window 'storage' event using useEffect and then refreshing the value if it changed.

Collapse
 
vtrpldn profile image
Vitor Paladini

Great article! Custom hooks are indeed a great to handle browser stuff like localStorage and browser events in an elegant way.