REACT

useReducerWithLocalStorage Hook

A custom React Hook combining useReducer with localStorage persistence (TypeScript support)

ReactHooksuseReducerLocalStorageStateManagement

Code

import { useReducer, useEffect, Reducer } from 'react';

type ReducerAction<T> = {
  type: string;
  payload?: T;
};

function useReducerWithLocalStorage<S, A extends ReducerAction<unknown>>(
  reducer: Reducer<S, A>,
  initialState: S,
  key: string
): [S, (action: A) => void] {
  // Initialize state from localStorage or initialState
  const getInitialState = (): S => {
    __TOKEN_26__ (typeof window === 'undefined') return initialState;
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialState;
    } __TOKEN_32__ (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
      return initialState;
    }
  };

  const [state, dispatch] = useReducer(reducer, initialState, getInitialState);

  // Persist state to localStorage on change
  useEffect(() => {
    __TOKEN_35__ (typeof window !== 'undefined') {
      try {
        window.localStorage.setItem(key, JSON.stringify(state));
      } __TOKEN_38__ (error) {
        console.warn(`Error setting localStorage key "${key}":`, error);
      }
    }
  }, [state, key]);

  return [state, dispatch];
}

// Usage example
// type CounterState = { count: number };
// type CounterAction = { type: 'INCREMENT' | 'DECREMENT' | 'RESET' };
// const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
//   switch (action.type) {
//     case 'INCREMENT': return { count: state.count + 1 };
//     case 'DECREMENT': return { count: state.count - 1 };
//     case 'RESET': return { count: 0 };
//     default: return state;
//   }
// };
// const [state, dispatch] = useReducerWithLocalStorage(counterReducer, { count: 0 }, 'counter-state');