-
-
Notifications
You must be signed in to change notification settings - Fork 430
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5bd5770
commit 4cf78c4
Showing
5 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
packages/usehooks-ts/src/useReadSessionStorage/useReadSessionStorage.demo.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { useReadSessionStorage } from '..' | ||
|
||
export default function Component() { | ||
// Assuming a value was set in session storage with this key | ||
const sessionId = useReadSessionStorage('sessionId') | ||
|
||
return <p>SessionId: {sessionId ?? 'not set'}</p> | ||
} |
13 changes: 13 additions & 0 deletions
13
packages/usehooks-ts/src/useReadSessionStorage/useReadSessionStorage.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
This React Hook allows you to read a value from session storage by its key. It can be useful if you just want to read without passing a default value. | ||
If the window object is not present (as in SSR) or if the value doesn't exist, `useReadSessionStorage()` will return `null`. | ||
|
||
**Options:** | ||
|
||
As with `useSessionStorage()`, there is some additional config you can pass to this hook with a second, `options` argument: | ||
|
||
- `parseAsJson: boolean` - defaults to `true`. If you have a previously set session storage value that you don't want to parse using `JSON.parse`, you can set this to `false` | ||
- `parser: (value: string | null) => T` - a custom parser if you have stored a session storage value with a custom serializer function | ||
|
||
Related hooks: | ||
|
||
If you want to be able to change the value, use [`useSessionStorage()`](/react-hook/use-session-storage). |
83 changes: 83 additions & 0 deletions
83
packages/usehooks-ts/src/useReadSessionStorage/useReadSessionStorage.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { renderHook } from '@testing-library/react-hooks/dom' | ||
|
||
import { useReadSessionStorage } from './useReadSessionStorage' | ||
|
||
class SessionStorageMock { | ||
store: Record<string, unknown> = {} | ||
|
||
clear() { | ||
this.store = {} | ||
} | ||
|
||
getItem(key: string) { | ||
return this.store[key] || null | ||
} | ||
|
||
setItem(key: string, value: unknown) { | ||
this.store[key] = value + '' | ||
} | ||
|
||
removeItem(key: string) { | ||
delete this.store[key] | ||
} | ||
} | ||
|
||
Object.defineProperty(window, 'sessionStorage', { | ||
value: new SessionStorageMock(), | ||
}) | ||
|
||
describe('useReadSessionStorage()', () => { | ||
beforeEach(() => { | ||
window.sessionStorage.clear() | ||
}) | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
test('returns null if no item in session storage', () => { | ||
const { result } = renderHook(() => useReadSessionStorage('key')) | ||
|
||
expect(result.current).toBe(null) | ||
}) | ||
|
||
test('returns object if no options passed', () => { | ||
const obj = { value: 'test' } | ||
window.sessionStorage.setItem('key', JSON.stringify(obj)) | ||
|
||
const { result } = renderHook(() => useReadSessionStorage('key')) | ||
|
||
expect(result.current).toStrictEqual(obj) | ||
}) | ||
|
||
test('returns string if parseAsJson is false', () => { | ||
window.sessionStorage.setItem('key', 'value') | ||
|
||
const { result } = renderHook(() => | ||
useReadSessionStorage('key', { parseAsJson: false }), | ||
) | ||
|
||
expect(result.current).toBe('value') | ||
}) | ||
|
||
test('returns expected value with custom parser', () => { | ||
window.sessionStorage.setItem('key', 'value') | ||
|
||
const { result } = renderHook(() => | ||
useReadSessionStorage('key', { parser: doubleLetters }), | ||
) | ||
|
||
expect(result.current).toBe('vvaalluuee') | ||
}) | ||
}) | ||
|
||
function doubleLetters(value: string | null) { | ||
if (value === null) { | ||
return '' | ||
} | ||
let result = '' | ||
for (let i = 0; i < value.length; i++) { | ||
result += value[i] + value[i] | ||
} | ||
return result | ||
} |
74 changes: 74 additions & 0 deletions
74
packages/usehooks-ts/src/useReadSessionStorage/useReadSessionStorage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { useCallback, useEffect, useState } from 'react' | ||
|
||
import { useEventListener } from '..' | ||
|
||
type Options<T> = { | ||
parseAsJson: boolean | ||
parser: (value: string | null) => T | ||
} | ||
|
||
type Value<T> = T | null | ||
|
||
export function useReadSessionStorage<T>( | ||
key: string, | ||
{ | ||
parseAsJson = true, | ||
parser = parseAsJson ? parseJSON : castValue, | ||
}: Partial<Options<T>> = {}, | ||
): Value<T> { | ||
// Get from session storage then | ||
// parse stored json or return initialValue | ||
|
||
const readValue = useCallback((): Value<T> => { | ||
// Prevent build error "window is undefined" but keep keep working | ||
if (typeof window === 'undefined') { | ||
return null | ||
} | ||
|
||
try { | ||
const item = window.sessionStorage.getItem(key) | ||
|
||
if (item) return parser(item) as T | ||
return null | ||
} catch (error) { | ||
console.warn(`Error reading sessionStorage key “${key}”:`, error) | ||
return null | ||
} | ||
}, [key, parser]) | ||
|
||
// State to store our value | ||
// Pass initial state function to useState so logic is only executed once | ||
const [storedValue, setStoredValue] = useState<Value<T>>(readValue) | ||
|
||
useEffect(() => { | ||
setStoredValue(readValue()) | ||
}, [readValue]) | ||
|
||
const handleStorageChange = useCallback( | ||
(event: StorageEvent | CustomEvent) => { | ||
if ((event as StorageEvent)?.key && (event as StorageEvent).key !== key) { | ||
return | ||
} | ||
setStoredValue(readValue()) | ||
}, | ||
[key, readValue], | ||
) | ||
|
||
// this only works for other documents, not the current one | ||
useEventListener('storage', handleStorageChange) | ||
|
||
// this is a custom event, triggered in writeValueTosessionStorage | ||
// See: useSessionStorage() | ||
useEventListener('session-storage', handleStorageChange) | ||
|
||
return storedValue | ||
} | ||
|
||
function parseJSON<T>(value: string | null): T { | ||
return JSON.parse(value ?? '') | ||
} | ||
|
||
// This is used when parseAsJSON === false | ||
function castValue<T>(value: string | null): T { | ||
return value as T | ||
} |