useSessionStorage()

Persist the state with session storage so that it remains after a page refresh. This can be useful 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), useSessionStorage() will return the default value.

Related hooks:

The Hook

1import {
2 Dispatch,
3 SetStateAction,
4 useCallback,
5 useEffect,
6 useState,
7} from 'react'
8
9import { useEventCallback } from '../useEventCallback'
10// See: https://usehooks-ts.com/react-hook/use-event-listener
11import { useEventListener } from '../useEventListener'
12
13declare global {
14 interface WindowEventMap {
15 'session-storage': CustomEvent
16 }
17}
18
19type SetValue<T> = Dispatch<SetStateAction<T>>
20
21function useSessionStorage<T>(key: string, initialValue: T): [T, SetValue<T>] {
22 // Get from session storage then
23 // parse stored json or return initialValue
24 const readValue = useCallback((): T => {
25 // Prevent build error "window is undefined" but keep keep working
26 if (typeof window === 'undefined') {
27 return initialValue
28 }
29
30 try {
31 const item = window.sessionStorage.getItem(key)
32 return item ? (parseJSON(item) as T) : initialValue
33 } catch (error) {
34 console.warn(`Error reading sessionStorage key “${key}”:`, error)
35 return initialValue
36 }
37 }, [initialValue, key])
38
39 // State to store our value
40 // Pass initial state function to useState so logic is only executed once
41 const [storedValue, setStoredValue] = useState<T>(readValue)
42
43 // Return a wrapped version of useState's setter function that ...
44 // ... persists the new value to sessionStorage.
45 const setValue: SetValue<T> = useEventCallback(value => {
46 // Prevent build error "window is undefined" but keeps working
47 if (typeof window == 'undefined') {
48 console.warn(
49 `Tried setting sessionStorage key “${key}” even though environment is not a client`,
50 )
51 }
52
53 try {
54 // Allow value to be a function so we have the same API as useState
55 const newValue = value instanceof Function ? value(storedValue) : value
56
57 // Save to session storage
58 window.sessionStorage.setItem(key, JSON.stringify(newValue))
59
60 // Save state
61 setStoredValue(newValue)
62
63 // We dispatch a custom event so every useSessionStorage hook are notified
64 window.dispatchEvent(new Event('session-storage'))
65 } catch (error) {
66 console.warn(`Error setting sessionStorage key “${key}”:`, error)
67 }
68 })
69
70 useEffect(() => {
71 setStoredValue(readValue())
72 // eslint-disable-next-line react-hooks/exhaustive-deps
73 }, [])
74
75 const handleStorageChange = useCallback(
76 (event: StorageEvent | CustomEvent) => {
77 if ((event as StorageEvent)?.key && (event as StorageEvent).key !== key) {
78 return
79 }
80 setStoredValue(readValue())
81 },
82 [key, readValue],
83 )
84
85 // this only works for other documents, not the current one
86 useEventListener('storage', handleStorageChange)
87
88 // this is a custom event, triggered in writeValueTosessionStorage
89 // See: useSessionStorage()
90 useEventListener('session-storage', handleStorageChange)
91
92 return [storedValue, setValue]
93}
94
95export default useSessionStorage
96
97// A wrapper for "JSON.parse()"" to support "undefined" value
98function parseJSON<T>(value: string | null): T | undefined {
99 try {
100 return value === 'undefined' ? undefined : JSON.parse(value ?? '')
101 } catch {
102 console.log('parsing error on', { value })
103 return undefined
104 }
105}

Usage

1import React from 'react'
2
3import { useSessionStorage } from 'usehooks-ts'
4
5export default function Component() {
6 const [value, setValue] = useSessionStorage('test-key', 0)
7
8 return (
9 <div>
10 <p>Count: {value}</p>
11 <button onClick={() => setValue(x => x + 1)}>Increment</button>
12 <button onClick={() => setValue(x => x - 1)}>Decrement</button>
13 </div>
14 )
15}

Edit on CodeSandbox

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