From 587de16ef5c85497d01e63247a578116d0605ff9 Mon Sep 17 00:00:00 2001 From: suyingtao Date: Fri, 13 Dec 2019 18:45:01 +0800 Subject: [PATCH 1/2] feat(useLocalStorage): add remove feature. (#229) --- docs/useLocalStorage.md | 3 ++- src/useLocalStorage.ts | 34 +++++++++++++++++++++++-------- stories/useLocalStorage.story.tsx | 7 +++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/useLocalStorage.md b/docs/useLocalStorage.md index a414fdad56..d7040eef9b 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 051685be63..0415f06090 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -1,23 +1,31 @@ -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 useLocalStorage = ( + key: string, + initialValue?: T, + raw?: boolean +): [T | null, 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 (typeof initialValue === 'undefined' && typeof localStorageValue !== 'string') { + return null; + } 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 +34,18 @@ const useLocalStorage = (key: string, initialValue?: T, raw?: boolean): [T, D } }); + const remove = useCallback(() => { + try { + localStorage.removeItem(key); + setState(null); + } catch { + // If user is in private mode or has storage restriction + // localStorage can throw. + } + }, [key, setState]); + useEffect(() => { + if (state === null) return; try { const serializedState = raw ? String(state) : JSON.stringify(state); localStorage.setItem(key, serializedState); @@ -35,8 +54,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 31f8e87003..f246c9607f 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}
+ + +
); }; From 1620e019fff94fb4a7a711fd3121ec02c7e99301 Mon Sep 17 00:00:00 2001 From: suyingtao Date: Mon, 16 Dec 2019 12:59:03 +0800 Subject: [PATCH 2/2] fix(useLocalStorage): using undefined for empty value instead of null --- src/useLocalStorage.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 0415f06090..7e6a3a64c1 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -5,21 +5,22 @@ type Dispatch
= (value: A) => void; type SetStateAction = S | ((prevState: S) => S); const noop = () => {}; +const isUndefined = (value?: any): boolean => typeof value === 'undefined'; const useLocalStorage = ( key: string, initialValue?: T, raw?: boolean -): [T | null, Dispatch>, () => void] => { +): [T | undefined, Dispatch>, () => void] => { if (!isClient) { return [initialValue as T, noop, noop]; } - const [state, setState] = useState(() => { + const [state, setState] = useState(() => { try { const localStorageValue = localStorage.getItem(key); - if (typeof initialValue === 'undefined' && typeof localStorageValue !== 'string') { - return null; + if (isUndefined(initialValue)) { + return undefined; } if (typeof localStorageValue !== 'string') { localStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue)); @@ -37,7 +38,7 @@ const useLocalStorage = ( const remove = useCallback(() => { try { localStorage.removeItem(key); - setState(null); + setState(undefined); } catch { // If user is in private mode or has storage restriction // localStorage can throw. @@ -45,7 +46,7 @@ const useLocalStorage = ( }, [key, setState]); useEffect(() => { - if (state === null) return; + if (isUndefined(state)) return; try { const serializedState = raw ? String(state) : JSON.stringify(state); localStorage.setItem(key, serializedState);