From c3fabac0d17d500bfa8ebd4938f2ee7be216cd1b Mon Sep 17 00:00:00 2001 From: chornos13 Date: Thu, 18 Feb 2021 12:02:55 +0700 Subject: [PATCH] feat(hooks): add hook usePersistedState --- .../__test__/usePersistedState.test.tsx | 96 +++++++++++++++++++ .../usePersistedState/usePersistedState.tsx | 28 ++++++ 2 files changed, 124 insertions(+) create mode 100644 src/hooks/usePersistedState/__test__/usePersistedState.test.tsx create mode 100644 src/hooks/usePersistedState/usePersistedState.tsx diff --git a/src/hooks/usePersistedState/__test__/usePersistedState.test.tsx b/src/hooks/usePersistedState/__test__/usePersistedState.test.tsx new file mode 100644 index 0000000..9b747d6 --- /dev/null +++ b/src/hooks/usePersistedState/__test__/usePersistedState.test.tsx @@ -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), + ) + }) +}) diff --git a/src/hooks/usePersistedState/usePersistedState.tsx b/src/hooks/usePersistedState/usePersistedState.tsx new file mode 100644 index 0000000..4698a68 --- /dev/null +++ b/src/hooks/usePersistedState/usePersistedState.tsx @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react' + +function usePersistedState(key: string, defaultValue: any) { + const [isLoading, setIsLoading] = useState(true) + const [value, setValue] = useState(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