useLocalStorage (React)

Save to localStorage whenever state changes and read from it for the first render. More primitive (and minimal) ancestor of useSyncedLocalStorage.

typescriptjavascript
import type { Dispatch, SetStateAction } from 'react'
import { useCallback, useEffect, useState } from 'react'
 
const useLocalStorage = <T>(
  key: string,
  initialValue: T
): [get: T, set: Dispatch<SetStateAction<T>>, clear: () => void] => {
  const [state, setState] = useState<T>(() => {
    const cached = localStorage.getItem(key)
 
    if (!cached) return initialValue
 
    try {
      return JSON.parse(cached) as T
    } catch (err) {
      return initialValue
    }
  })
 
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(state))
  }, [state, key])
 
  const clearState = useCallback(() => {
    setState(initialValue)
  }, [initialValue])
 
  return [state, setState, clearState]
}
import { useCallback, useEffect, useState } from 'react'
 
const useLocalStorage = (key, initialValue) => {
  const [state, setState] = useState(() => {
    const cached = localStorage.getItem(key)
 
    if (!cached) return initialValue
 
    try {
      return JSON.parse(cached)
    } catch (err) {
      return initialValue
    }
  })
 
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(state))
  }, [state, key])
 
  const clearState = useCallback(() => {
    setState(initialValue)
  }, [initialValue])
 
  return [state, setState, clearState]
}

Info

  • We use useCallback for the clearState function to prevent unnecessary re-renders if you use the clearState function inside a useEffect or useCallback and list it in the dependency array.

Warning

  • This version of the hook with only work client-side and will throw an error you server-render a component using the hook
  • You should only run the hook once per key at a time - the hook doesn't handle changes when another instance of the hook updates the matching key in localStorage. If you do want to handle that, check out useSyncedLocalStorage

Usage

typescriptjavascript
const Example = () => {
  const [input, setInput] = useLocalStorage('example:input', '')
 
  return (
    <>
      <input value={input} onChange={e => setInput(e.target.value)} />
    </>
  )
}
const Example = () => {
  const [input, setInput] = useLocalStorage('example:input', '')
 
  return (
    <>
      <input value={input} onChange={e => setInput(e.target.value)} />
    </>
  )
}

Demo

Watch the value for the example:input key in the Application > Local Storage section in your devtools as you type in the input. If you're on mobile, enter some text into the input, and reload the page - when you do, you'll see the same text that you entered pre-filled.

Created 26/03/21Updated 30/08/23
Found a mistake, or want to suggest an improvement? Source on GitHub here
and see edit history here