useLocalStorage()
Persist the state with local storage so that it remains after a page refresh. This can be useful for a dark theme.
This hook is used in the same way as useState except that you must pass the storage key in the 1st parameter.
If the window object is not present (as in SSR), useLocalStorage()
will return the default value.
Side notes:
- If you really want to create a dark theme switch, see useDarkMode().
- If you just want read value from local storage, see useReadLocalStorage().
Related hooks:
The Hook
1import {2 Dispatch,3 SetStateAction,4 useCallback,5 useEffect,6 useState,7} from 'react'89import { useEventCallback, useEventListener } from 'usehooks-ts'1011declare global {12 interface WindowEventMap {13 'local-storage': CustomEvent14 }15}1617type SetValue<T> = Dispatch<SetStateAction<T>>1819function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>] {20 // Get from local storage then21 // parse stored json or return initialValue22 const readValue = useCallback((): T => {23 // Prevent build error "window is undefined" but keeps working24 if (typeof window === 'undefined') {25 return initialValue26 }2728 try {29 const item = window.localStorage.getItem(key)30 return item ? (parseJSON(item) as T) : initialValue31 } catch (error) {32 console.warn(`Error reading localStorage key “${key}”:`, error)33 return initialValue34 }35 }, [initialValue, key])3637 // State to store our value38 // Pass initial state function to useState so logic is only executed once39 const [storedValue, setStoredValue] = useState<T>(readValue)4041 // Return a wrapped version of useState's setter function that ...42 // ... persists the new value to localStorage.43 const setValue: SetValue<T> = useEventCallback(value => {44 // Prevent build error "window is undefined" but keeps working45 if (typeof window === 'undefined') {46 console.warn(47 `Tried setting localStorage key “${key}” even though environment is not a client`,48 )49 }5051 try {52 // Allow value to be a function so we have the same API as useState53 const newValue = value instanceof Function ? value(storedValue) : value5455 // Save to local storage56 window.localStorage.setItem(key, JSON.stringify(newValue))5758 // Save state59 setStoredValue(newValue)6061 // We dispatch a custom event so every useLocalStorage hook are notified62 window.dispatchEvent(new Event('local-storage'))63 } catch (error) {64 console.warn(`Error setting localStorage key “${key}”:`, error)65 }66 })6768 useEffect(() => {69 setStoredValue(readValue())70 // eslint-disable-next-line react-hooks/exhaustive-deps71 }, [])7273 const handleStorageChange = useCallback(74 (event: StorageEvent | CustomEvent) => {75 if ((event as StorageEvent)?.key && (event as StorageEvent).key !== key) {76 return77 }78 setStoredValue(readValue())79 },80 [key, readValue],81 )8283 // this only works for other documents, not the current one84 useEventListener('storage', handleStorageChange)8586 // this is a custom event, triggered in writeValueToLocalStorage87 // See: useLocalStorage()88 useEventListener('local-storage', handleStorageChange)8990 return [storedValue, setValue]91}9293export default useLocalStorage9495// A wrapper for "JSON.parse()"" to support "undefined" value96function parseJSON<T>(value: string | null): T | undefined {97 try {98 return value === 'undefined' ? undefined : JSON.parse(value ?? '')99 } catch {100 console.log('parsing error on', { value })101 return undefined102 }103}
Usage
1import { useLocalStorage } from 'usehooks-ts'23// Usage4export default function Component() {5 const [isDarkTheme, setDarkTheme] = useLocalStorage('darkTheme', true)67 const toggleTheme = () => {8 setDarkTheme((prevValue: boolean) => !prevValue)9 }1011 return (12 <button onClick={toggleTheme}>13 {`The current theme is ${isDarkTheme ? `dark` : `light`}`}14 </button>15 )16}
See a way to make this page better?
Edit there »