diff --git a/docs/useLocalStorage.md b/docs/useLocalStorage.md index a414fdad..d7040eef 100644 --- a/docs/useLocalStorage.md +++ b/docs/useLocalStorage.md @@ -9,13 +9,14 @@ React side-effect hook that manages a single `localStorage` key. import {useLocalStorage} from 'react-use'; const Demo = () => { - const [value, setValue] = useLocalStorage('my-key', 'foo'); + const [value, setValue, remove] = useLocalStorage('my-key', 'foo'); return (
Value: {value}
+
); }; diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 051685be..7e6a3a64 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,23 +1,32 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { isClient } from './util'; type Dispatch = (value: A) => void; type SetStateAction = S | ((prevState: S) => S); -const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, Dispatch>] => { +const noop = () => {}; +const isUndefined = (value?: any): boolean => typeof value === 'undefined'; + +const useLocalStorage = ( + key: string, + initialValue?: T, + raw?: boolean +): [T | undefined, Dispatch>, () => void] => { if (!isClient) { - return [initialValue as T, () => {}]; + return [initialValue as T, noop, noop]; } - const [state, setState] = useState(() => { + const [state, setState] = useState(() => { try { const localStorageValue = localStorage.getItem(key); + if (isUndefined(initialValue)) { + return undefined; + } if (typeof localStorageValue !== 'string') { localStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue)); return initialValue; - } else { - return raw ? localStorageValue : JSON.parse(localStorageValue || 'null'); } + return raw ? localStorageValue : JSON.parse(localStorageValue || 'null'); } catch { // If user is in private mode or has storage restriction // localStorage can throw. JSON.parse and JSON.stringify @@ -26,7 +35,18 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D } }); + const remove = useCallback(() => { + try { + localStorage.removeItem(key); + setState(undefined); + } catch { + // If user is in private mode or has storage restriction + // localStorage can throw. + } + }, [key, setState]); + useEffect(() => { + if (isUndefined(state)) return; try { const serializedState = raw ? String(state) : JSON.stringify(state); localStorage.setItem(key, serializedState); @@ -35,8 +55,7 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D // localStorage can throw. Also JSON.stringify can throw. } }, [state]); - - return [state, setState]; + return [state, setState, remove]; }; export default useLocalStorage; diff --git a/stories/useLocalStorage.story.tsx b/stories/useLocalStorage.story.tsx index 31f8e870..f246c960 100644 --- a/stories/useLocalStorage.story.tsx +++ b/stories/useLocalStorage.story.tsx @@ -5,12 +5,19 @@ import ShowDocs from './util/ShowDocs'; const Demo = () => { const [value, setValue] = useLocalStorage('hello-key', 'foo'); + const [removableValue, setRemovableValue, remove] = useLocalStorage('removeable-key'); return (
Value: {value}
+
+
+
Removable Value: {removableValue}
+ + +
); };