-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(hooks): add hook usePersistedState
- Loading branch information
Showing
2 changed files
with
124 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
src/hooks/usePersistedState/__test__/usePersistedState.test.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,96 @@ | ||
import { renderHook, act } from '@testing-library/react-hooks' | ||
import usePersistedState from 'hooks/usePersistedState/usePersistedState' | ||
|
||
const mockGetItem = jest.fn() | ||
const mockSetItem = jest.fn() | ||
Storage.prototype.getItem = mockGetItem | ||
Storage.prototype.setItem = mockSetItem | ||
|
||
describe('basic function persisted state', () => { | ||
afterEach(() => { | ||
mockGetItem.mockReset() | ||
mockSetItem.mockReset() | ||
}) | ||
|
||
test('should return defaultValue if no saved data', () => { | ||
// arrange | ||
mockGetItem.mockReturnValue(null) | ||
const defaultValue = 'anyDefaultValue' | ||
const { result } = renderHook(() => | ||
usePersistedState('anyKey', defaultValue), | ||
) | ||
|
||
const [value] = result.current | ||
expect(value).toEqual(defaultValue) | ||
}) | ||
|
||
test('should return stored value for that key if key hook changed', () => { | ||
// arrange | ||
mockGetItem.mockImplementation((key) => { | ||
return { | ||
anyInitialKey: 'anyValueInitialKey', | ||
anyChangedKey: 'anyValueChangedKey', | ||
}[key] | ||
}) | ||
const { result, rerender } = renderHook( | ||
(isNewKey) => | ||
usePersistedState( | ||
isNewKey ? 'anyChangedKey' : 'anyInitialKey', | ||
'anyDefaultValue', | ||
), | ||
{ | ||
initialProps: false, | ||
}, | ||
) | ||
expect(result.current[0]).toEqual('anyValueInitialKey') | ||
|
||
// act | ||
rerender(true) | ||
|
||
// assert | ||
expect(result.current[0]).toEqual('anyValueChangedKey') | ||
}) | ||
|
||
test('should parsing JSON savedData from stored value', () => { | ||
// arrange | ||
const jsonData = JSON.stringify('anyJSONData') | ||
mockGetItem.mockReturnValue(jsonData) | ||
const { result } = renderHook(() => | ||
usePersistedState('anyKey', 'anyDefaultValue'), | ||
) | ||
|
||
const [value] = result.current | ||
expect(value).toEqual(JSON.parse(jsonData)) | ||
}) | ||
|
||
test('should return stored value if failed parsing to JSON', () => { | ||
// arrange | ||
const invalidJSONData = 'anyInvalidJSONData' | ||
mockGetItem.mockReturnValue(invalidJSONData) | ||
const { result } = renderHook(() => | ||
usePersistedState('anyKey', 'anyDefaultValue'), | ||
) | ||
|
||
const [value] = result.current | ||
expect(value).toEqual(invalidJSONData) | ||
}) | ||
|
||
test('should saved data to localStorage when call setValue', () => { | ||
// arrange | ||
const savedData = 'anyStoreData' | ||
const { result } = renderHook(() => | ||
usePersistedState('anyKey', 'anyDefaultValue'), | ||
) | ||
mockSetItem.mockImplementation((key, value) => { | ||
mockGetItem.mockReturnValue(value) | ||
}) | ||
|
||
// act | ||
const [, setValue] = result.current | ||
act(() => setValue(savedData)) | ||
|
||
expect(mockGetItem.getMockImplementation()()).toEqual( | ||
JSON.stringify(savedData), | ||
) | ||
}) | ||
}) |
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,28 @@ | ||
import { useEffect, useState } from 'react' | ||
|
||
function usePersistedState<T = any>(key: string, defaultValue: any) { | ||
const [isLoading, setIsLoading] = useState(true) | ||
const [value, setValue] = useState<T>(defaultValue) | ||
|
||
useEffect(() => { | ||
const persistedValue = localStorage.getItem(key) | ||
if (![null, undefined].includes(persistedValue)) { | ||
let savedValue | ||
try { | ||
savedValue = JSON.parse(persistedValue) | ||
} catch (e) { | ||
savedValue = persistedValue | ||
} | ||
setValue(savedValue) | ||
} | ||
setIsLoading(false) | ||
}, [key]) | ||
|
||
useEffect(() => { | ||
localStorage.setItem(key, JSON.stringify(value)) | ||
}, [key, value]) | ||
|
||
return [value, setValue, isLoading] as const | ||
} | ||
|
||
export default usePersistedState |