How to Create a Persistent State Hook with localStorage in React

16 days ago2 minutes read

In React, state usually disappears when you refresh the page. Imagine building a form, a theme toggle, or a shopping cart — once the user reloads, everything resets. That’s not always a good experience.

Luckily, the browser gives us localStorage, which can save data in the user’s browser even after closing or reloading the tab. By combining localStorage with React hooks, we can create a custom hook that persists state automatically.

In this post, we’ll build a reusable useLocalState hook.

Why not just use useState?

React’s built-in useState is great for temporary state:

jsxconst [count, setCount] = useState(0)

But after a refresh, count resets back to 0. If we want state that survives reloads, we need localStorage.

Step 1: Setting up the Hook

jsximport { useState, useEffect } from "react"

function useLocalState(key, defaultValue) {
  const [state, setState] = useState(() => {
    try {
      const stored = localStorage.getItem(key)
      return stored ? JSON.parse(stored) : defaultValue
    } catch {
      return defaultValue
    }
  })

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

  return [state, setState]
}

export default useLocalState

How it Works

  1. Initialization with useState
    • On the first render, the hook checks if there’s a saved value in localStorage under the given key.
    • If yes → it loads that value.
    • If not → it uses the provided default.
  2. Sync with useEffect
    • Every time the state changes, we save the new value to localStorage with JSON.stringify.
  3. Return value
    • Just like useState, we return [state, setState], so you can use it the same way.

Using the hook

jsximport React from "react"
import useLocalState from "./useLocalState"

function Counter() {
  const [count, setCount] = useLocalState("count", 0)

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(c => c + 1)}>Increase</button>
      <button onClick={() => setCount(c => c - 1)}>Decrease</button>
    </div>
  )
}

export default Counter

Now, if you refresh the page, the counter value stays the same 🎉

Real-World Use Cases

  • Dark mode toggle → Save the theme preference.
  • Forms → Keep inputs filled after reload.
  • Shopping carts → Persist items until checkout.
  • Drafts → Save unsent posts or notes.

ShahriyarAlam

Copythight © All Rights Reserved.