useLocalStorage()

Persist the state with local storage so that it remains after a page refresh. This can be useful for a dark theme or to record session information. 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.

Note: If you really want to create a dark theme switch, see useDarkMode().

The Hook

1import { Dispatch, SetStateAction, useEffect, useState } from 'react'
2
3function useLocalStorage<T>(
4 key: string,
5 initialValue?: T | (() => T),
6): [T, Dispatch<SetStateAction<T>>] {
7 // Get from local storage then
8 // parse stored json or return initialValue
9 const readValue = () => {
10 // Prevent build error "window is undefined" but keep keep working
11 if (typeof window === 'undefined') {
12 return initialValue
13 }
14
15 try {
16 const item = window.localStorage.getItem(key)
17 return item ? JSON.parse(item) : initialValue
18 } catch (error) {
19 console.warn(`Error reading localStorage key “${key}”:`, error)
20 return initialValue
21 }
22 }
23
24 // State to store our value
25 // Pass initial state function to useState so logic is only executed once
26 const [storedValue, setStoredValue] = useState<T>(readValue)
27
28 // Return a wrapped version of useState's setter function that ...
29 // ... persists the new value to localStorage.
30 const setValue: Dispatch<SetStateAction<T>> = value => {
31 // Prevent build error "window is undefined" but keeps working
32 if (typeof window == 'undefined') {
33 console.warn(
34 `Tried setting localStorage key “${key}” even though environment is not a client`,
35 )
36 }
37
38 try {
39 // Allow value to be a function so we have the same API as useState
40 const newValue = value instanceof Function ? value(storedValue) : value
41
42 // Save to local storage
43 window.localStorage.setItem(key, JSON.stringify(newValue))
44
45 // Save state
46 setStoredValue(newValue)
47
48 // We dispatch a custom event so every useLocalStorage hook are notified
49 window.dispatchEvent(new Event('local-storage'))
50 } catch (error) {
51 console.warn(`Error setting localStorage key “${key}”:`, error)
52 }
53 }
54
55 useEffect(() => {
56 setStoredValue(readValue())
57 // eslint-disable-next-line react-hooks/exhaustive-deps
58 }, [])
59
60 useEffect(() => {
61 const handleStorageChange = () => {
62 setStoredValue(readValue())
63 }
64
65 // this only works for other documents, not the current one
66 window.addEventListener('storage', handleStorageChange)
67
68 // this is a custom event, triggered in writeValueToLocalStorage
69 window.addEventListener('local-storage', handleStorageChange)
70
71 return () => {
72 window.removeEventListener('storage', handleStorageChange)
73 window.removeEventListener('local-storage', handleStorageChange)
74 }
75 // eslint-disable-next-line react-hooks/exhaustive-deps
76 }, [])
77
78 return [storedValue, setValue]
79}
80
81export default useLocalStorage

Usage

1import React from 'react'
2
3import useLocalStorage from './useLocalStorage'
4
5// Usage
6export default function Component() {
7 const [isDarkTheme, setDarkTheme] = useLocalStorage('darkTheme', true)
8
9 const toggleTheme = () => {
10 setDarkTheme(prevValue => !prevValue)
11 }
12
13 return (
14 <button onClick={toggleTheme}>
15 {`The current theme is ${isDarkTheme ? `dark` : `light`}`}
16 </button>
17 )
18}

See a way to make this page better?
Edit there »