This is a hook for persisting state to localStorage. Update the error handling to suit your needs.

import { useCallback, useState } from "react";

type UpdaterFn<TState> = (currentState: TState) => TState;

type Result<TState> = [
  state: TState,
  wrappedSetState: (update: TState | UpdaterFn<TState>) => void

export default function useLocalStorage<TState>(
  key: string,
  initialValue: TState
): Result<TState> {
  const [state, setState] = useState<TState>(() => {
    try {
      const item = window.localStorage.getItem(key);

      return item ? JSON.parse(item) : initialValue;
    } catch (error) {

      return initialValue;

  const wrappedSetState = useCallback(
    (update) => {
      try {
        setState((currentState) => {
          const nextState =
            typeof update === "function" ? update(currentState) : update;

          window.localStorage.setItem(key, JSON.stringify(nextState));
          return nextState;
      } catch (error) {

  return [state, wrappedSetState];

Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.

Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Good software by good people.
Visit to learn more
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.
Logo for Introduction to State Machines and XState
Introduction to State Machines and XState
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.