Use browser's local to manage and persist data using this hook. This is perticularly useful for dark theme, user data management etc.

The hook almost identical to useState() hook. It requires two parameters, a key to assign, and an initial value as fallback. When accessed from server environment or the window is not available the default value is used.

The hook listens to any changes on storage, and local-storage (custom event) event and updates the state accordingly. Any change to local storage will trigger the hook (line 36,37).

It requires useEvent hook to be used. Check out the hook here for more information.

use-local-storage.ts
import { useEffect, useState } from 'react'
import useEvent from './use-event'
// type definitions
import type { Dispatch, SetStateAction } from 'react'

type SetValue<T> = Dispatch<SetStateAction<T>>
declare global {
	interface WindowEventMap {
		'local-storage': CustomEvent
	}
}

function parseJSON<T>(value?: string): T | undefined {
	if (!value) return
	return JSON.parse(value ?? '')
}

export function useLocalStorage<T>(
	key: string,
	initial: T,
): [T, SetValue<T>] {
	const [storedValue, setStoredValue] = useState<T>(initial)

	const getValue = (): T => {
		// error handling for serverside
		if (typeof window === 'undefined')
			return initial
		try {
			const item = window.localStorage.getItem(key)
			return item ? (parseJSON(item) as T) : initial
		} catch (err) {
			console.warn(`Error reading localStorage key "${key}":`, err)
			return initial
		}
	}

	const setValue: SetValue<T> = (value) => {
		if (typeof window === 'undefined')
			console.warn(`Tried setting localStorage key "${key}" even though environment is not a client`)
		try {
			const newValue = value instanceof Function ? value(storedValue) : value
			window.localStorage.setItem(key, JSON.stringify(newValue))
			setStoredValue(newValue)
			window.dispatchEvent(new Event('local-storage'))
		} catch (err) {
			console.warn(`Error setting localStorage key "${key}":`, err)
		}
	}

	const handleChange = () => setStoredValue(getValue())
	// eslint-disable-next-line
	useEffect(handleChange, [])
	useEvent('storage', handleChange)
	useEvent('local-storage', handleChange)

	return [storedValue, setValue]
}

export default useLocalStorage

  • Key: The key to assign in localStorage.
  • Initial: The initial value to use if the key is not found in localStorage or a fallback value.

import { useLocalStorage } from 'hooks/local-storage'
function App(props) {
	const [theme, setTheme] = useLocalStorage('theme', 'light')
	// ...
	return (
		<span>Theme Name: {theme}</span>
		// ...
		<button onClick={()=>{setTheme('dark')}}>Dark Theme</button>
	)
}