Skip to content

Commit

Permalink
feat(hooks): add hook usePersistedState
Browse files Browse the repository at this point in the history
  • Loading branch information
chornos13 committed Feb 18, 2021
1 parent e2c0c7b commit c3fabac
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
96 changes: 96 additions & 0 deletions src/hooks/usePersistedState/__test__/usePersistedState.test.tsx
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),
)
})
})
28 changes: 28 additions & 0 deletions src/hooks/usePersistedState/usePersistedState.tsx
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

0 comments on commit c3fabac

Please sign in to comment.