Skip to content

Commit e8345c8

Browse files
committed
✨ bring all new improvements from the use-local-storage-state repo
1 parent e93acd0 commit e8345c8

File tree

2 files changed

+43
-65
lines changed

2 files changed

+43
-65
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"@typescript-eslint/parser": "^5.27.1",
5858
"confusing-browser-globals": "^1.0.11",
5959
"eslint": "^8.17.0",
60-
"eslint-config-strictest": "^0.4.0",
60+
"eslint-config-strictest": "^0.8.1",
6161
"eslint-formatter-pretty": "^4.0.0",
6262
"eslint-plugin-promise": "^6.0.0",
6363
"eslint-plugin-react": "^7.29.4",

src/useSessionStorageState.ts

Lines changed: 42 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Dispatch, SetStateAction } from 'react'
22
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
33

4-
// in memory fallback used then `sessionStorage` throws an error
4+
// in memory fallback used when `sessionStorage` throws an error
55
export const inMemoryData = new Map<string, unknown>()
66

77
export type SessionStorageOptions<T> = {
@@ -26,7 +26,7 @@ export type SessionStorageState<T> = [
2626

2727
export default function useSessionStorageState(
2828
key: string,
29-
options?: Omit<SessionStorageOptions<unknown>, 'defaultValue'>,
29+
options?: SessionStorageOptions<undefined>,
3030
): SessionStorageState<unknown>
3131
export default function useSessionStorageState<T>(
3232
key: string,
@@ -40,27 +40,8 @@ export default function useSessionStorageState<T = undefined>(
4040
key: string,
4141
options?: SessionStorageOptions<T | undefined>,
4242
): SessionStorageState<T | undefined> {
43-
const [defaultValue] = useState(options?.defaultValue)
44-
45-
// SSR support
46-
// - on the server, return a constant value
47-
// - this makes the implementation simpler and smaller because the `sessionStorage` object is
48-
// `undefined` on the server
49-
if (typeof window === 'undefined') {
50-
return [
51-
defaultValue,
52-
(): void => {},
53-
{
54-
isPersistent: true,
55-
removeItem: (): void => {},
56-
},
57-
]
58-
}
59-
6043
const serializer = options?.serializer
61-
// disabling ESLint because the above if statement can be executed only on the server. the value
62-
// of `window` can't change between calls.
63-
// eslint-disable-next-line react-hooks/rules-of-hooks
44+
const [defaultValue] = useState(options?.defaultValue)
6445
return useBrowserSessionStorageState(
6546
key,
6647
defaultValue,
@@ -77,35 +58,18 @@ function useBrowserSessionStorageState<T>(
7758
parse: (value: string) => unknown = parseJSON,
7859
stringify: (value: unknown) => string = JSON.stringify,
7960
): SessionStorageState<T | undefined> {
80-
// store default value in sessionStorage:
81-
// - initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26
82-
// issues that were caused by incorrect initial and secondary implementations:
83-
// - https://github.com/astoilkov/use-local-storage-state/issues/30
84-
// - https://github.com/astoilkov/use-local-storage-state/issues/33
85-
if (
86-
!inMemoryData.has(key) &&
87-
defaultValue !== undefined &&
88-
goodTry(() => sessionStorage.getItem(key)) === null
89-
) {
90-
// reasons for `sessionStorage` to throw an error:
91-
// - maximum quota is exceeded
92-
// - under Mobile Safari (since iOS 5) when the user enters private mode
93-
// `sessionStorage.setItem()` will throw
94-
// - trying to access sessionStorage object when cookies are disabled in Safari throws
95-
// "SecurityError: The operation is insecure."
96-
goodTry(() => sessionStorage.setItem(key, stringify(defaultValue)))
97-
}
98-
9961
// we keep the `parsed` value in a ref because `useSyncExternalStore` requires a cached version
100-
const storageValue = useRef<{ item: string | null; parsed: T | undefined }>({
101-
item: null,
102-
parsed: defaultValue,
62+
const storageItem = useRef<{ string: string | null; parsed: T | undefined }>({
63+
string: null,
64+
parsed: undefined,
10365
})
66+
10467
const value = useSyncExternalStore(
68+
// useSyncExternalStore.subscribe
10569
useCallback(
10670
(onStoreChange) => {
107-
const onChange = (localKey: string): void => {
108-
if (key === localKey) {
71+
const onChange = (sessionKey: string): void => {
72+
if (key === sessionKey) {
10973
onStoreChange()
11074
}
11175
}
@@ -117,40 +81,56 @@ function useBrowserSessionStorageState<T>(
11781
[key],
11882
),
11983

120-
// eslint-disable-next-line react-hooks/exhaustive-deps
84+
// useSyncExternalStore.getSnapshot
12185
() => {
122-
const item = goodTry(() => sessionStorage.getItem(key)) ?? null
86+
const string = goodTry(() => sessionStorage.getItem(key)) ?? null
12387

12488
if (inMemoryData.has(key)) {
125-
storageValue.current = {
126-
item,
127-
parsed: inMemoryData.get(key) as T | undefined,
128-
}
129-
} else if (item !== storageValue.current.item) {
89+
storageItem.current.parsed = inMemoryData.get(key) as T | undefined
90+
} else if (string !== storageItem.current.string) {
13091
let parsed: T | undefined
13192

13293
try {
133-
parsed = item === null ? defaultValue : (parse(item) as T)
94+
parsed = string === null ? defaultValue : (parse(string) as T)
13495
} catch {
13596
parsed = defaultValue
13697
}
13798

138-
storageValue.current = {
139-
item,
140-
parsed,
141-
}
99+
storageItem.current.parsed = parsed
142100
}
143101

144-
return storageValue.current.parsed
102+
storageItem.current.string = string
103+
104+
// store default value in sessionStorage:
105+
// - initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26
106+
// issues that were caused by incorrect initial and secondary implementations:
107+
// - https://github.com/astoilkov/use-local-storage-state/issues/30
108+
// - https://github.com/astoilkov/use-local-storage-state/issues/33
109+
if (string === null && defaultValue !== undefined) {
110+
// reasons for `sessionStorage` to throw an error:
111+
// - maximum quota is exceeded
112+
// - under Mobile Safari (since iOS 5) when the user enters private mode
113+
// `sessionStorage.setItem()` will throw
114+
// - trying to access sessionStorage object when cookies are disabled in Safari throws
115+
// "SecurityError: The operation is insecure."
116+
// eslint-disable-next-line no-console
117+
goodTry(() => {
118+
const string = stringify(defaultValue)
119+
sessionStorage.setItem(key, string)
120+
storageItem.current = { string, parsed: defaultValue }
121+
})
122+
}
123+
124+
return storageItem.current.parsed
145125
},
146126

147-
// istanbul ignore next
127+
// useSyncExternalStore.getServerSnapshot
148128
() => defaultValue,
149129
)
150130
const setState = useCallback(
151131
(newValue: SetStateAction<T | undefined>): void => {
152132
const value =
153-
newValue instanceof Function ? newValue(storageValue.current.parsed) : newValue
133+
newValue instanceof Function ? newValue(storageItem.current.parsed) : newValue
154134

155135
// reasons for `sessionStorage` to throw an error:
156136
// - maximum quota is exceeded
@@ -226,7 +206,5 @@ function parseJSON(value: string): unknown {
226206
function goodTry<T>(tryFn: () => T): T | undefined {
227207
try {
228208
return tryFn()
229-
} catch {
230-
return undefined
231-
}
209+
} catch {}
232210
}

0 commit comments

Comments
 (0)