Skip to content

Commit 9e6a57f

Browse files
John Richard Chipps-HardingPaulo Miguel Ferreira Jorge
andauthored
Adds reset & fixes Typescript bug (#5)
* Adds reset * A test to verify the found bug in isolation * Enhance Documentation * Tidy * Tidy * Linting * chore(): clean up tests * chore(): update key naming convention to match others Co-authored-by: Paulo Miguel Ferreira Jorge <[email protected]>
1 parent df5a945 commit 9e6a57f

File tree

3 files changed

+113
-21
lines changed

3 files changed

+113
-21
lines changed

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Just swap out your `useState` calls with `useLocalState` to persist the data bet
1313
```javascript
1414
import useLocalState from "@phntms/use-local-state";
1515

16-
const [token, setToken] = useLocalState("USER_TOKEN", "");
16+
const [value, setValue, resetValue] = useLocalState("KEY", "");
1717
```
1818

1919
## Installation
@@ -59,14 +59,15 @@ interface Bookmark {
5959
}
6060

6161
const BookmarkExample = () = {
62-
const [bookmarks, setBookmarks] = useLocalState<Bookmark[]>("BOOKMARKS", []);
62+
const [bookmarks, setBookmarks, clearBookmarks] = useLocalState<Bookmark[]>("BOOKMARKS", []);
6363

6464
const addBookmark = (bookmark: Bookmark) => setBookmarks([...bookmarks, bookmark]);
6565

6666
return (
6767
<>
6868
<h1>Bookmarks</h2>
6969
<NewBookmark add={addBookmark} />
70+
<button onClick={clearBookmarks}>Clear Bookmarks</button>
7071
<ul>
7172
{bookmarks.map(((bookmark, i) => (
7273
<li key={i}>
@@ -88,7 +89,11 @@ const BookmarkExample = () = {
8889
8990
### Output
9091
91-
An array containing the value and a function to set the value. Signature is exactly like the standard [React.useState](https://reactjs.org/docs/hooks-state.html) hook.
92+
An array containing the value, a function to set the value and another function to reset the value.
93+
94+
- `[0]` : The value of the data stored in LocalStorage or the defaultValue if not set.
95+
- `[1]` : A function to set the value stored in LocalStorage. Signature is exactly like the standard [React.useState](https://reactjs.org/docs/hooks-state.html) hook.
96+
- `[2]` : A function to reset the data stored in the LocalStorage.
9297
9398
[npm-image]: https://img.shields.io/npm/v/@phntms/use-local-state.svg?style=flat-square&logo=react
9499
[npm-url]: https://npmjs.org/package/@phntms/use-local-state

src/index.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import { useState } from "react";
22

33
import { SUPPORTED } from "./utils";
44

5-
const useLocalState = <T>(
5+
type Dispatch<A> = (value: A) => void;
6+
type SetStateAction<S> = S | ((prevState: S) => S);
7+
8+
const useLocalState = <S>(
69
key: string,
7-
defaultValue: T
8-
): [T, (v: T | ((v: T) => T)) => void] => {
9-
const [value, setValue] = useState<T>(() => {
10-
const toStore =
11-
typeof defaultValue === "function" ? defaultValue() : defaultValue;
10+
defaultValue: S | (() => S)
11+
): [S, Dispatch<SetStateAction<S>>, () => void] => {
12+
const [value, setValue] = useState<S>(() => {
13+
const isCallable = (value: unknown): value is () => S =>
14+
typeof value === "function";
15+
const toStore = isCallable(defaultValue) ? defaultValue() : defaultValue;
1216
if (!SUPPORTED) return toStore;
1317
const item = window.localStorage.getItem(key);
1418
try {
@@ -18,15 +22,23 @@ const useLocalState = <T>(
1822
}
1923
});
2024

21-
const setLocalStateValue = (newValue: T | ((v: T) => T)) => {
22-
const isCallable = (value: unknown): value is (v: T) => T =>
25+
const setLocalStateValue = (newValue: SetStateAction<S>) => {
26+
const isCallable = (value: unknown): value is (prevState: S) => S =>
2327
typeof value === "function";
2428
const toStore = isCallable(newValue) ? newValue(value) : newValue;
2529
if (SUPPORTED) window.localStorage.setItem(key, JSON.stringify(toStore));
2630
setValue(toStore);
2731
};
2832

29-
return [value, setLocalStateValue];
33+
const reset = () => {
34+
const isCallable = (value: unknown): value is (prevState: S) => S =>
35+
typeof value === "function";
36+
const toStore = isCallable(defaultValue) ? defaultValue() : defaultValue;
37+
setValue(toStore);
38+
if (SUPPORTED) window.localStorage.removeItem(key);
39+
};
40+
41+
return [value, setLocalStateValue, reset];
3042
};
3143

3244
export default useLocalState;

test/index.test.tsx

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe("useLocalState()", () => {
5656
expect(todos).toEqual(values);
5757
});
5858

59-
it("accepts callback as a value", async () => {
59+
it("accepts callback as an initial value", async () => {
6060
const key = "todos";
6161
const values = ["first", "second"];
6262
const callback = () => values;
@@ -66,12 +66,32 @@ describe("useLocalState()", () => {
6666
expect(todos).toEqual(values);
6767
});
6868

69+
it("accepts callback as a initial value and can be updated", async () => {
70+
const key = "todos";
71+
const values = ["first", "second"];
72+
const callback = () => values;
73+
const { result } = renderHook(() => useLocalState(key, callback));
74+
75+
const [initialValues] = result.current;
76+
expect(initialValues).toEqual(values);
77+
78+
const newValue = ["third", "fourth"];
79+
act(() => {
80+
const setValue = result.current[1];
81+
setValue(newValue);
82+
});
83+
84+
const [changedValues] = result.current;
85+
expect(changedValues).toEqual(newValue);
86+
});
87+
6988
it("can update value as string", async () => {
7089
const key = "key";
7190
const value = "something";
72-
7391
const { result } = renderHook(() => useLocalState(key, value));
74-
expect(result.current[0]).toEqual(value);
92+
93+
const [initialValue] = result.current;
94+
expect(initialValue).toEqual(value);
7595

7696
const newValue = "something else";
7797
act(() => {
@@ -87,9 +107,10 @@ describe("useLocalState()", () => {
87107
it("can update value as object", async () => {
88108
const key = "key";
89109
const value = { something: "something" };
90-
91110
const { result } = renderHook(() => useLocalState(key, value));
92-
expect(result.current[0]).toEqual(value);
111+
112+
const [initialValue] = result.current;
113+
expect(initialValue).toEqual(value);
93114

94115
const newValue = { something: "else" };
95116
act(() => {
@@ -118,8 +139,7 @@ describe("useLocalState()", () => {
118139
expect(todos).toEqual(newValues);
119140
});
120141

121-
// todo(): this logic could be super handy...
122-
it("updates state with callback function", () => {
142+
it("updates state with callback function", async () => {
123143
const key = "todos";
124144
const values = ["first", "second"];
125145
const { result } = renderHook(() => useLocalState(key, values));
@@ -182,7 +202,6 @@ describe("useLocalState()", () => {
182202
it("can handle `undefined` values", async () => {
183203
const key = "todos";
184204
const values = ["first", "second"];
185-
186205
const { result } = renderHook(() =>
187206
useLocalState<string[] | undefined>(key, values)
188207
);
@@ -199,7 +218,6 @@ describe("useLocalState()", () => {
199218
it("can handle `null` values", async () => {
200219
const key = "todos";
201220
const values = ["first", "second"];
202-
203221
const { result } = renderHook(() =>
204222
useLocalState<string[] | null>(key, values)
205223
);
@@ -212,4 +230,61 @@ describe("useLocalState()", () => {
212230
const [value] = result.current;
213231
expect(value).toBe(null);
214232
});
233+
234+
it("can reset to default value", async () => {
235+
if (!SUPPORTED) return;
236+
237+
const key = "key";
238+
const value = "something";
239+
const { result } = renderHook(() => useLocalState(key, value));
240+
241+
const [initialValue] = result.current;
242+
expect(initialValue).toEqual(value);
243+
244+
const newValue = "something else";
245+
act(() => {
246+
const setValue = result.current[1];
247+
setValue(newValue);
248+
});
249+
250+
const [changedValue] = result.current;
251+
expect(changedValue).toEqual(newValue);
252+
253+
act(() => {
254+
const resetValue = result.current[2];
255+
resetValue();
256+
});
257+
258+
const [resetValue] = result.current;
259+
expect(resetValue).toEqual(value);
260+
expect(localStorage.getItem(key)).toEqual(null);
261+
});
262+
263+
it("can reset to default value as callback", async () => {
264+
const key = "todos";
265+
const values = ["first", "second"];
266+
const callback = () => values;
267+
const { result } = renderHook(() => useLocalState(key, callback));
268+
269+
const [initialValues] = result.current;
270+
expect(initialValues).toEqual(values);
271+
272+
const newValues = ["third", "fourth"];
273+
act(() => {
274+
const setValues = result.current[1];
275+
setValues(newValues);
276+
});
277+
278+
const [changedValues] = result.current;
279+
expect(changedValues).toEqual(newValues);
280+
281+
act(() => {
282+
const resetValues = result.current[2];
283+
resetValues();
284+
});
285+
286+
const [resetValues] = result.current;
287+
expect(resetValues).toEqual(values);
288+
expect(localStorage.getItem(key)).toEqual(null);
289+
});
215290
});

0 commit comments

Comments
 (0)