Skip to content

Commit f6d96d8

Browse files
committed
[ADD] useLocalStorage hook
1 parent d04adfb commit f6d96d8

8 files changed

Lines changed: 638 additions & 603 deletions

File tree

apps/react-tools-demo/src/components/hooks/useLocalStorage/UseLocalStorage.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import { useCallback, useEffect } from "react";
1+
import { useCallback } from "react";
2+
import { useLocalStorage } from "../../../../../../packages/react-tools/src/hooks";
23

34
/**
45
DEMO
56
*/
67
const UseLocalStorage = () => {
8+
const [state, setState, , remove] = useLocalStorage({ key: "key" });
79
const func = useCallback(() => {
8-
window.localStorage.setItem(Math.random() > 0.5 ? "demo" : "prv", "2");
9-
window.dispatchEvent(new Event("storage"));
10-
}, []);
10+
setState(Math.random() > .5 ? "MAGGIORE" : "MINORE");
11+
}, [setState]);
1112

12-
const clear = useCallback(() => window.localStorage.clear(), []);
13+
const clear = useCallback(() => remove(), [remove]);
1314

14-
useEffect(() => {
15-
const listener = (evt: Event) => console.log(evt);
16-
window.addEventListener("storage", listener);
1715

18-
return () => window.removeEventListener("storage", listener);
19-
},[])
2016
return (<>
17+
{state}
2118
<button onClick={func}>Prova</button>
2219
<button onClick={clear}>clear</button>
2320
</>);

apps/react-tools-demo/src/markdown/useEventDispatcher.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const UseEventDispatcher = () => {
2323
});
2424

2525
return (<>
26-
state is {state}
26+
<em>State is: </em> {state}
2727
<br />
2828
<form noValidate onSubmit={onSubmit}>
2929
<input type="text" ref={inputRef}/>
@@ -38,7 +38,7 @@ export const UseEventDispatcher = () => {
3838
> - An _inputRef_ ref variable attacched to an input element contained in a form.
3939
> - An dispatch function returned from _useEventDispatcher_ with _inputRef_ as element.
4040
> - An onSubmit function to handle form onSubmit that invokes _dispatch_ function with a CustomEvent("demo") which detail is valued with input value taken from onSubmit event.
41-
> - A useEventListener of type _demo_, on element _inputRef_ and a listener that takes CustomEvent and invokes setState with event detail.
41+
> - A useEventListener of type _demo_, on element _inputRef_ and with a listener that takes CustomEvent and invokes setState with event detail.
4242
4343

4444
## API

apps/react-tools-demo/src/markdown/useLocalStorage.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,16 @@
55

66
```tsx
77
const UseLocalStorage = () => {
8+
const [state, setState, , remove] = useLocalStorage({ key: "key" });
89
const func = useCallback(() => {
9-
window.localStorage.setItem(Math.random() > 0.5 ? "demo" : "prv", "2");
10-
window.dispatchEvent(new Event("storage"));
11-
}, []);
10+
setState(Math.random() > 0.5);
11+
}, [setState]);
1212

13-
const clear = useCallback(() => window.localStorage.clear(), []);
13+
const clear = useCallback(() => remove(), [remove]);
1414

15-
useEffect(() => {
16-
const listener = (evt: Event) => console.log(evt);
17-
window.addEventListener("storage", listener);
1815

19-
return () => window.removeEventListener("storage", listener);
20-
},[])
2116
return (<>
17+
{state}
2218
<button onClick={func}>Prova</button>
2319
<button onClick={clear}>clear</button>
2420
</>);
@@ -35,7 +31,7 @@ export { UseLocalStorage };
3531
## API
3632

3733
```tsx
38-
useLocalStorage = <T>(key: string, initialState?: T | (()=>T))
34+
useLocalStorage = <T>({ key, initialState, opts = { serializer: JSON.stringify, deserializer: JSON.parse } }: { key: string, initialState?: T | (() => T), opts?: { serializer: (item: T) => string, deserializer: (item: string) => T } }): [T, Dispatch<SetStateAction<T>>, ()=>T, ()=>void]
3935
```
4036

4137
> ### Params

packages/react-tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- [x] useReducerGetReset
88
- [x] useReducerHistory
99
- [x] useReducerHistoryGetter
10-
- [-] useLocalStorage
10+
- [-] useLocalStorage (Manca documentazione)
1111
- [ ] useSessionStorage
1212
- [ ] useMap
1313
- [ ] useSet

packages/react-tools/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { useStateHistory } from './useStateHistory';
44
export { useStateHistoryGetter } from './useStateHistoryGetter';
55
export { useReducerGetReset } from "./useReducerGetReset";
66
export { useReducerHistory } from './useReducerHistory';
7+
export { useLocalStorage } from './useLocalStorage';
78
export { useMemoizedFunction } from './useMemoizedFunction';
89
export { useMemoCompare } from "./useMemoCompare";
910
export { useMemoDeepCompare } from './useMemoDeepCompare';
Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,68 @@
1-
import { useState } from "react"
2-
import { useMemoizedFunction } from ".";
1+
import { Dispatch, SetStateAction, useRef, useState } from "react"
2+
import { useEventDispatcher, useEventListener, useMemoizedFunction } from ".";
33

4-
export const useLocalStorage = <T>(key: string, initialState?: T | (()=>T)) => {
5-
const [internalState, setState] = useState(() => {
4+
export const useLocalStorage = <T>({ key, initialState, opts = { serializer: JSON.stringify, deserializer: JSON.parse } }: { key: string, initialState?: T | (() => T), opts?: { serializer: (item: T) => string, deserializer: (item: string) => T } }): [T, Dispatch<SetStateAction<T>>, ()=>T, ()=>void] => {
5+
const dispatch = useEventDispatcher();
6+
const [internalState, setState] = useState<T>(() => {
67
const state = localStorage.getItem(key);
78
if (state != null) {
8-
return JSON.parse(state);
9+
return opts.deserializer(state);
910
} else if (initialState) {
1011
const result: T = initialState instanceof Function
1112
? initialState()
1213
: internalState;
13-
localStorage.setItem(key, JSON.stringify(result));
14+
localStorage.setItem(key, opts.serializer(result));
1415
return result;
1516
} else {
16-
return null;
17+
return null as T;
1718
}
1819
})
1920

21+
const listener = useRef((evt: Event) => {
22+
let newValue;
23+
if (evt instanceof CustomEvent) {
24+
newValue = opts.deserializer(evt.detail.newValue);
25+
setState(newValue);
26+
} else {
27+
const typedEvent = evt as StorageEvent;
28+
if (typedEvent.key === key) {
29+
newValue = typedEvent.newValue ? opts.deserializer(typedEvent.newValue) : opts.deserializer("null");
30+
setState(newValue);
31+
}
32+
}
33+
});
2034

35+
useEventListener({ type: "storage", listener: listener.current, listenerOpts: { capture: true } });
2136

22-
const updateState = useMemoizedFunction((value: T | ((state: T) => T)) => {
23-
const computedValue = value instanceof Function
37+
useEventListener({ type: "local-strg", listener: listener.current, listenerOpts: { capture: true } });
38+
39+
const updateState: Dispatch<SetStateAction<T>> = useMemoizedFunction((value: T | ((state: T) => T)) => {
40+
const computedValue = opts.serializer(value instanceof Function
2441
? value(internalState)
25-
: value;
26-
localStorage.setItem(key, JSON.stringify(computedValue));
42+
: value);
43+
localStorage.setItem(key, computedValue);
44+
dispatch(new CustomEvent(
45+
"local-strg",
46+
{
47+
detail: {
48+
key,
49+
newValue: computedValue,
50+
}
51+
}
52+
));
53+
});
54+
55+
const remove = useRef(() => {
56+
localStorage.removeItem(key);
57+
setState(null as T);
2758
});
2859

60+
const getter = useMemoizedFunction(() => internalState);
2961

62+
return [
63+
internalState,
64+
updateState,
65+
getter,
66+
remove.current
67+
];
3068
}

packages/react-tools/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
useStateHistoryGetter,
1111
useReducerGetReset,
1212
useReducerHistory,
13+
useLocalStorage,
1314
useMemoCompare,
1415
useMemoDeepCompare,
1516
useCallbackCompare,

0 commit comments

Comments
 (0)