diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index def6ad804a..4ca3e3cc12 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -15,9 +15,14 @@ const useLocalStorage = ( key: string, initialValue?: T, options?: parserOptions -): [T | undefined, Dispatch>, () => void] => { +): [ + T | undefined, + Dispatch>, + () => void, + Dispatch> +] => { if (!isBrowser) { - return [initialValue as T, noop, noop]; + return [initialValue as T, noop, noop, noop]; } if (!key) { throw new Error('useLocalStorage key may not be falsy'); @@ -82,6 +87,20 @@ const useLocalStorage = ( [key, setState] ); + // eslint-disable-next-line react-hooks/rules-of-hooks + const safeSet: Dispatch> = useCallback( + (valOrFunc) => { + if (localStorage.getItem(key)) { + console.warn( + `You are attempting to set a key that is already in use, the action has been prevented. To remove this warning, use set` + ); + return; + } + set(valOrFunc); + }, + [key, setState] + ); + // eslint-disable-next-line react-hooks/rules-of-hooks const remove = useCallback(() => { try { @@ -93,7 +112,7 @@ const useLocalStorage = ( } }, [key, setState]); - return [state, set, remove]; + return [state, set, remove, safeSet]; }; export default useLocalStorage; diff --git a/stories/useLocalStorage.story.tsx b/stories/useLocalStorage.story.tsx index e5c446708e..19e7268197 100644 --- a/stories/useLocalStorage.story.tsx +++ b/stories/useLocalStorage.story.tsx @@ -5,15 +5,21 @@ import ShowDocs from './util/ShowDocs'; const Demo = () => { const [value, setValue] = useLocalStorage('hello-key', 'foo'); - const [removableValue, setRemovableValue, remove] = useLocalStorage('removeable-key'); + const [removableValue, setRemovableValue, remove, setSafeValue] = + useLocalStorage('removeable-key'); return (
-
Value: {value}
+
Set Value: {value}


+
Safe Value: {value}
+ + +
+
Removable Value: {removableValue}
diff --git a/tests/useLocalStorage.test.ts b/tests/useLocalStorage.test.ts index 1ec54dd637..055984f070 100644 --- a/tests/useLocalStorage.test.ts +++ b/tests/useLocalStorage.test.ts @@ -3,6 +3,8 @@ import 'jest-localstorage-mock'; import { renderHook, act } from '@testing-library/react-hooks'; describe(useLocalStorage, () => { + let consoleWarningSpy = jest.spyOn(global.console, 'warn').mockImplementation(() => {}); + afterEach(() => { localStorage.clear(); jest.clearAllMocks(); @@ -46,6 +48,34 @@ describe(useLocalStorage, () => { expect(localStorage.__STORE__.foo).toEqual('"baz"'); }); + it('safeSet will not update a value if it already exists localStorage', () => { + const { result, rerender } = renderHook(() => useLocalStorage('some_unused_key')); + + const [, , , safeSet] = result.current; + act(() => safeSet('foo')); + rerender(); + + expect(localStorage.__STORE__.some_unused_key).toEqual('"foo"'); + + act(() => safeSet('bar')); + rerender(); + + expect(localStorage.__STORE__.some_unused_key).toEqual('"foo"'); + }); + + it('safeSet will warn if you are trying to update a value that already exists localStorage', () => { + const { result, rerender } = renderHook(() => useLocalStorage('some_unused_key')); + + const [, , , safeSet] = result.current; + act(() => safeSet('foo')); + rerender(); + + act(() => safeSet('bar')); + rerender(); + + expect(consoleWarningSpy).toBeCalled(); + }); + it('should return undefined if no initialValue provided and localStorage empty', () => { const { result } = renderHook(() => useLocalStorage('some_key'));