Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 6 of the Intermediate React: Hooks, State & Data Patterns course
ReactState ManagementJune 25, 20264 min read

Building a useLocalStorage Hook: Persistence & State Management

Learn to build a robust useLocalStorage hook. Master generic storage, JSON serialization, and cross-tab synchronization to keep your React app state persistent.

ReactHookslocalStoragePersistenceState ManagementCustom Hooksjavascriptfrontend

Previously in this course, we explored Introduction to Custom Hooks to encapsulate and reuse logic across your application. While those lessons focused on abstracting component behavior, today we’re tackling a critical real-world requirement: data persistence.

By default, React state lives in memory. Refresh the page, and it’s gone. To build a production-grade dashboard, we need to bridge the gap between volatile component state and the browser's persistent storage. We’ll build a useLocalStorage hook that handles serialization, provides a clean API, and ensures our UI stays consistent even if the user opens multiple tabs.

Persistence with custom hooks

The goal is to create a hook that mimics the useState API but automatically syncs the value to localStorage. We need to handle three core challenges:

  1. JSON Serialization: localStorage only stores strings; we need to store objects and arrays.
  2. Initial State Logic: We shouldn't parse localStorage on every single render.
  3. Cross-tab Sync: When the user updates a setting in one tab, the others should reflect that change immediately.

Implementing the Hook

We’ll start by creating useLocalStorage.js. This hook will take a key and an initialValue.

JAVASCRIPT
import { useState, useEffect } from CE9178">'react';

export function useLocalStorage(key, initialValue) {
  // 1. Get initial value from storage or fallback to initialValue
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // 2. Update storage whenever state changes
  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
}

Handling Cross-Tab Synchronization

The basic implementation above works for a single tab, but it fails if the user has your dashboard open in two different windows. When localStorage changes in one tab, the other tabs don't automatically trigger a re-render.

We can solve this by listening to the storage event, which fires globally across the same origin.

JAVASCRIPT
useEffect(() => {
  const handleStorageChange = (e) => {
    if (e.key === key && e.newValue !== null) {
      setStoredValue(JSON.parse(e.newValue));
    }
  };

  window.addEventListener(CE9178">'storage', handleStorageChange);
  return () => window.removeEventListener(CE9178">'storage', handleStorageChange);
}, [key]);

Worked Example: Dashboard Theme Toggle

In our running project, let's use this hook to persist the user's dashboard theme preference.

JSX
import { useLocalStorage } from CE9178">'./hooks/useLocalStorage';

function DashboardSettings() {
  const [theme, setTheme] = useLocalStorage(CE9178">'dashboard-theme', CE9178">'light');

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme(theme === CE9178">'light' ? CE9178">'dark' : CE9178">'light')}>
        Toggle Theme
      </button>
    </div>
  );
}

By using useLocalStorage here, the theme state survives page refreshes and stays in sync if the user toggles it in a different dashboard view. This is a massive improvement over standard useState for user preferences.

Hands-on Exercise

  1. Create the useLocalStorage hook file as shown above.
  2. Refactor your dashboard's "User Settings" component to use this hook instead of local state.
  3. Test it: Open your dashboard in two browser tabs. Toggle the setting in one tab and verify the other updates automatically.
  4. Edge Case: Try setting the value to null or an invalid JSON string in the browser DevTools Application tab and ensure your hook catches the error gracefully.

Common Pitfalls

  • JSON.parse failures: Always wrap your JSON.parse in a try/catch block. If a user manually edits their localStorage to an invalid value, your entire app could crash on mount.
  • Performance overhead: Don't use localStorage for high-frequency state updates (like tracking mouse coordinates). It involves synchronous disk I/O, which can cause jank. Reserve it for configuration or form data.
  • SSR Incompatibility: If you move to a framework like Next.js, window will be undefined on the server. Always check if (typeof window !== 'undefined') or only access storage inside useEffect.

Recap

We’ve moved beyond simple state management by bridging memory with persistent browser storage. We learned how to:

  • Encapsulate persistence logic using custom hooks.
  • Handle JSON serialization safely with error boundaries.
  • Synchronize state across multiple browser tabs using the storage event.

This pattern is a foundational piece of our dashboard project, ensuring that user preferences and cached UI states are preserved across sessions.

Up next: We'll dive into Complex State with useReducer, where we'll learn how to handle more intricate state transitions that go beyond simple key-value pairs.

Previous lessonIntroduction to Custom HooksNext lesson Refactoring Dashboard Settings
Back to Blog

Similar Posts

ReactJune 25, 20263 min read

Working with LocalStorage: Persisting React State Across Reloads

Learn to use localStorage to persist your React app's state across browser refreshes. Master the art of syncing local data with your component state.

Read more
ReactJune 25, 20263 min read

Introduction to Context API: Avoiding Prop Drilling in React

Learn how to use the Context API and useContext to share data across your React application, effectively eliminating prop drilling for cleaner code.

Part of the course

Intermediate React: Hooks, State & Data Patterns

intermediate · Lesson 6 of 48

  1. 1

    Mastering useRef for DOM Access

    4 min
  2. 2

    Persistent Mutable Values with useRef

    4 min
  3. 3

    Memoizing Expensive Calculations with useMemo

    3 min
Read more
ReactJune 25, 20263 min read

Building a Movie Filter Toggle in React: A Beginner's Guide

Master the art of UI state management by adding a filter toggle to your movie app. Learn to control list rendering using boolean state and checkbox inputs.

Read more
4

Optimizing Function References with useCallback

3 min
  • 5

    Introduction to Custom Hooks

    4 min
  • 6

    Building a useLocalStorage Hook

    4 min
  • 7

    Refactoring Dashboard Settings

    4 min
  • 8

    Complex State with useReducer

    3 min
  • 9

    Managing Object-Based State

    3 min
  • 10

    Introduction to Context API

    3 min
  • 11

    Architecting Global State with Context and Reducer

    3 min
  • 12

    Implementing Theme Context

    4 min
  • 13

    Structuring State for Performance

    Coming soon
  • 14

    Handling Authentication State

    Coming soon
  • 15

    Integrating Reducers with Auth State

    Coming soon
  • 16

    Introduction to React Router

    Coming soon
  • 17

    Dynamic Routing with URL Parameters

    Coming soon
  • 18

    Nested Routes and Layouts

    Coming soon
  • 19

    Protected Routes for Authenticated Views

    Coming soon
  • 20

    Programmatic Navigation

    Coming soon
  • 21

    Building the Dashboard Navigation Structure

    Coming soon
  • 22

    Asynchronous Data Lifecycle

    Coming soon
  • 23

    Caching Strategies with React Query

    Coming soon
  • 24

    Mutations and Data Updates

    Coming soon
  • 25

    Synchronizing Client and Server State

    Coming soon
  • 26

    Integrating Live Data into the Dashboard

    Coming soon
  • 27

    Error Handling and Loading UI

    Coming soon
  • 28

    Controlled vs Uncontrolled Components

    Coming soon
  • 29

    Real-time Form Validation

    Coming soon
  • 30

    Schema-based Validation with Zod

    Coming soon
  • 31

    Handling Multi-step Forms

    Coming soon
  • 32

    Optimizing Form Submissions

    Coming soon
  • 33

    Performance Profiling with React DevTools

    Coming soon
  • 34

    Refactoring for Scalability

    Coming soon
  • 35

    Finalizing Dashboard Data Flow

    Coming soon
  • 36

    Deploying the Application

    Coming soon
  • 37

    Advanced Hook Composition

    Coming soon
  • 38

    Implementing Middleware for State

    Coming soon
  • 39

    Advanced Context Patterns

    Coming soon
  • 40

    Router Loaders and Data Prefetching

    Coming soon
  • 41

    Complex Route Guards

    Coming soon
  • 42

    Handling Large Datasets in UI

    Coming soon
  • 43

    Testing Hooks and Components

    Coming soon
  • 44

    Managing Global Modals

    Coming soon
  • 45

    Implementing Keyboard Shortcuts

    Coming soon
  • 46

    Optimizing Asset Loading

    Coming soon
  • 47

    Internationalization Basics

    Coming soon
  • 48

    Managing WebSocket Connections

    Coming soon
  • View full course