Skip to content

Commit

Permalink
streamich#2540 - implemented safeSet for useLocalStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
geanify committed Feb 19, 2024
1 parent ade8d39 commit 8bd7971
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 5 deletions.
25 changes: 22 additions & 3 deletions src/useLocalStorage.ts
Expand Up @@ -15,9 +15,14 @@ const useLocalStorage = <T>(
key: string,
initialValue?: T,
options?: parserOptions<T>
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {
): [
T | undefined,
Dispatch<SetStateAction<T | undefined>>,
() => void,
Dispatch<SetStateAction<T | undefined>>
] => {
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');
Expand Down Expand Up @@ -82,6 +87,20 @@ const useLocalStorage = <T>(
[key, setState]
);

// eslint-disable-next-line react-hooks/rules-of-hooks
const safeSet: Dispatch<SetStateAction<T | undefined>> = 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 {
Expand All @@ -93,7 +112,7 @@ const useLocalStorage = <T>(
}
}, [key, setState]);

return [state, set, remove];
return [state, set, remove, safeSet];
};

export default useLocalStorage;
10 changes: 8 additions & 2 deletions stories/useLocalStorage.story.tsx
Expand Up @@ -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 (
<div>
<div>Value: {value}</div>
<div>Set Value: {value}</div>
<button onClick={() => setValue('bar')}>bar</button>
<button onClick={() => setValue('baz')}>baz</button>
<br />
<br />
<div>Safe Value: {value}</div>
<button onClick={() => setSafeValue('bar')}>bar</button>
<button onClick={() => setSafeValue('baz')}>baz</button>
<br />
<br />
<div>Removable Value: {removableValue}</div>
<button onClick={() => setRemovableValue('foo')}>foo</button>
<button onClick={() => setRemovableValue('bar')}>bar</button>
Expand Down
30 changes: 30 additions & 0 deletions tests/useLocalStorage.test.ts
Expand Up @@ -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();
Expand Down Expand Up @@ -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'));

Expand Down

0 comments on commit 8bd7971

Please sign in to comment.