0
0
ReactHow-ToBeginner · 4 min read

How to Create useLocalStorage Hook in React for Persistent State

Create a useLocalStorage hook by using React's useState and useEffect to read from and write to localStorage. This hook initializes state from localStorage and updates it whenever the state changes, keeping data persistent across page reloads.
📐

Syntax

The useLocalStorage hook takes two arguments: a key string to identify the stored item, and an initialValue used if no stored value exists. It returns an array with the current value and a setter function to update it.

  • key: The name under which data is saved in localStorage.
  • initialValue: The default value if localStorage is empty.
  • Returns: [value, setValue] similar to useState.
javascript
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = React.useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  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);
    }
  };

  return [storedValue, setValue];
}
💻

Example

This example shows a counter that saves its count in localStorage. The count persists even after refreshing the page.

javascript
import React from 'react';

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

  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);
    }
  };

  return [storedValue, setValue];
}

export default function Counter() {
  const [count, setCount] = useLocalStorage('count', 0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}
Output
Count: 0 (initially) or saved count after reload Clicking Increment increases count and updates localStorage Clicking Reset sets count to 0 and updates localStorage
⚠️

Common Pitfalls

  • Not parsing JSON from localStorage causes errors or wrong data types.
  • Not handling exceptions when accessing localStorage can crash the app in private mode or restricted browsers.
  • Updating state without syncing localStorage leads to stale data.
  • Using useEffect to update localStorage can cause extra renders; better to update inside the setter function.
javascript
/* Wrong way: updating localStorage inside useEffect causes double renders */
function useLocalStorageWrong(key, initialValue) {
  const [value, setValue] = React.useState(() => {
    const item = window.localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

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

  return [value, setValue];
}

/* Right way: update localStorage inside setter to avoid extra renders */
function useLocalStorageRight(key, initialValue) {
  const [storedValue, setStoredValue] = React.useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  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);
    }
  };

  return [storedValue, setValue];
}
📊

Quick Reference

Tips for useLocalStorage hook:

  • Always JSON.parse when reading and JSON.stringify when writing.
  • Wrap localStorage access in try/catch to handle errors gracefully.
  • Update localStorage inside the setter function to avoid extra renders.
  • Use a function form in setter to get latest state safely.
  • Use descriptive keys to avoid collisions in localStorage.

Key Takeaways

Create useLocalStorage hook using useState with lazy initialization from localStorage.
Update localStorage inside the setter function to keep state and storage in sync without extra renders.
Always parse JSON when reading and stringify when writing to localStorage.
Wrap localStorage calls in try/catch to avoid errors in restricted environments.
Use descriptive keys and function form setters for safe and clear state updates.