Skip to content

Commit

Permalink
✨ throw an error to promote createLocalStorageStateHook()
Browse files Browse the repository at this point in the history
Using `useLocalStorageState()` multipe times with the same key actually doesn't synchronize. `createLocalStorageStateHook()` should be used instead. I am introducing an error to guide users to use the correct API.
  • Loading branch information
astoilkov committed Mar 24, 2020
1 parent dc36e1e commit 68953dd
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 12 deletions.
25 changes: 23 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState, useLayoutEffect, useCallback, Dispatch, SetStateAction } from 'react'

const initializedStorageKeys = new Set<string>()

export default function useLocalStorageState<T>(
key: string,
defaultValue?: T,
Expand All @@ -21,6 +23,21 @@ export default function useLocalStorageState<T>(
[key],
)

useLayoutEffect(() => {
if (initializedStorageKeys.has(key)) {
throw new Error(
`Multiple instances of useLocalStorageState() initialized with the same key. ` +
`Use createLocalStorageStateHook() instead. ` +
`Look at the example here: ` +
`https://github.com/astoilkov/use-local-storage-state#create-local-storage-state-hook-example`,
)
} else {
initializedStorageKeys.add(key)
}

return () => void initializedStorageKeys.delete(key)
}, [])

/**
* Checks for changes across tabs and iframe's.
*/
Expand All @@ -40,18 +57,22 @@ export default function useLocalStorageState<T>(
}

export function createLocalStorageStateHook<T>(
name: string,
key: string,
defaultValue?: T,
): () => [T, Dispatch<SetStateAction<T>>] {
const updates: ((newValue: T | ((value: T) => T)) => void)[] = []
return function useLocalStorageStateHook(): [T, Dispatch<SetStateAction<T>>] {
const [value, setValue] = useLocalStorageState<T>(name, defaultValue)
const [value, setValue] = useLocalStorageState<T>(key, defaultValue)
const updateValue = useCallback((newValue: T | ((value: T) => T)) => {
for (const update of updates) {
update(newValue)
}
}, [])

useLayoutEffect(() => {
initializedStorageKeys.delete(key)
}, [])

useLayoutEffect(() => {
updates.push(setValue)
return () => void updates.splice(updates.indexOf(setValue), 1)
Expand Down
35 changes: 25 additions & 10 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,7 @@ describe('useLocalStorageState()', () => {
})

it('storage event updates state', () => {
const { result: resultA } = renderHook(() =>
useLocalStorageState('todos', ['first', 'second']),
)
const { result: resultB } = renderHook(() =>
useLocalStorageState('todos', ['first', 'second']),
)
const { result } = renderHook(() => useLocalStorageState('todos', ['first', 'second']))

/**
* #WET 2020-03-19T8:55:25+02:00
Expand All @@ -79,11 +74,8 @@ describe('useLocalStorageState()', () => {
)
})

const [todosA] = resultA.current
const [todosA] = result.current
expect(todosA).toEqual(['third', 'forth'])

const [todosB] = resultB.current
expect(todosB).toEqual(['third', 'forth'])
})

it('storage event updates state to default value', () => {
Expand Down Expand Up @@ -144,6 +136,29 @@ describe('useLocalStorageState()', () => {
const [todos] = result.current
expect(todos).toEqual(['third', 'forth'])
})

it('throws an error on two instances with the same key', () => {
const consoleError = console.error
console.error = () => {}

expect(() => {
renderHook(() => {
useLocalStorageState('todos', ['first', 'second'])
useLocalStorageState('todos', ['second', 'third'])
})
}).toThrow()

console.error = consoleError
})

it('does not throw an error on two instances with different keys', () => {
expect(() => {
renderHook(() => {
useLocalStorageState('todosA', ['first', 'second'])
useLocalStorageState('todosB', ['second', 'third'])
})
}).not.toThrow()
})
})

describe('createLocalStorageStateHook()', () => {
Expand Down

0 comments on commit 68953dd

Please sign in to comment.