Skip to content

Commit 879528a

Browse files
KrosFirepikax
andauthored
Feat/improved local and session storage (#1003)
* fix(useLocalStorage): Changed useLocalStorage and useSessionStorage so that it works according to the documentation. Removed setSync from useSessionStorage as it is unusable. Removed a bug of saving value into storage, just after it was read from it. Added useDebounce parameter so that storage is updated instantly (it's usefull for example when saving accessToken in localStorage and reading from it rather than from ref). Fixed a bug of giving the ref as a defaultValue and saving the ref into a storage, rather than the value of a ref * feat(interfaceAndComments): Added/updated interfaces and cmments for local and session storage * fix(localStoarage): fixed tests * fix(webStorage): fixed vue 2 build * fix(webStorage): testing piepeline * fix(webStorage): testing piepeline Co-authored-by: Carlos Rodrigues <[email protected]>
1 parent 64c8a90 commit 879528a

File tree

7 files changed

+214
-112
lines changed

7 files changed

+214
-112
lines changed

docs/composable/storage/localStorage.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
```js
88
import { useLocalStorage } from "vue-composable";
99

10-
const localStorage = useLocalStorage(key, defaultValue?, sync?);
10+
const localStorage = useLocalStorage<T=string>(key, defaultValue?, sync?, useDebounce?);
1111
```
1212
1313
| Parameters | Type | Required | Default | Description |
1414
| ------------ | --------------------- | -------- | ----------- | --------------------------------------------------- |
15-
| key | `string, ref<string>` | `true` | | Key that will be used to store in localStorage |
16-
| defaultValue | `object` | `false` | `undefined` | default value stored in the localStorage |
15+
| key | `string, Ref<string>` | `true` | | Key that will be used to store in localStorage |
16+
| defaultValue | `T, Ref<T>` | `false` | `undefined` | default value stored in the localStorage |
1717
| sync | `Boolean` | `false` | `true` | sets the storage to sync automatically between tabs |
18+
| useDebounce | `Boolean` | `false` | `true` | updates value in localStorage once every 10ms |
1819
1920
## State
2021
@@ -26,10 +27,10 @@ import { useLocalStorage } from "vue-composable";
2627
const { supported, storage } = useLocalStorage(key);
2728
```
2829
29-
| State | Type | Description |
30-
| --------- | ---------- | ------------------------------------------- |
31-
| supported | `boolean` | returns true is `localStorage` is available |
32-
| storage | `Ref<any>` | handler with localStorage value |
30+
| State | Type | Description |
31+
| --------- | --------------------- | ------------------------------------------- |
32+
| supported | `boolean` | returns true is `localStorage` is available |
33+
| storage | `Ref<T \| undefined>` | handler with localStorage value |
3334
3435
## Methods
3536

docs/composable/storage/sessionStorage.md

+15-16
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
```js
88
import { useSessionStorage } from "vue-composable";
99

10-
const SessionStorage = useSessionStorage(key, defaultValue?, sync?);
10+
const SessionStorage = useSessionStorage<T=string>(key, defaultValue?, useDebounce?);
1111
```
1212
13-
| Parameters | Type | Required | Default | Description |
14-
| ------------ | --------------------- | -------- | ----------- | --------------------------------------------------- |
15-
| key | `string, ref<string>` | `true` | | Key that will be used to store in SessionStorage |
16-
| defaultValue | `object` | `false` | `undefined` | default value stored in the SessionStorage |
17-
| sync | `Boolean` | `false` | `true` | sets the storage to sync automatically between tabs |
13+
| Parameters | Type | Required | Default | Description |
14+
| ------------ | --------------------- | -------- | ----------- | ------------------------------------------------ |
15+
| key | `string, Ref<string>` | `true` | | Key that will be used to store in SessionStorage |
16+
| defaultValue | `T, Ref<T>` | `false` | `undefined` | default value stored in the SessionStorage |
17+
| useDebounce | `Boolean` | `false` | `true` | updates value in sessionStorage once every 10ms |
1818
1919
## State
2020
@@ -26,10 +26,10 @@ import { useSessionStorage } from "vue-composable";
2626
const { supported, storage } = useSessionStorage(key);
2727
```
2828
29-
| State | Type | Description |
30-
| --------- | ---------- | --------------------------------------------- |
31-
| supported | `boolean` | returns true is `SessionStorage` is available |
32-
| storage | `Ref<any>` | handler with SessionStorage value |
29+
| State | Type | Description |
30+
| --------- | --------------------- | --------------------------------------------- |
31+
| supported | `boolean` | returns true is `SessionStorage` is available |
32+
| storage | `Ref<T \| undefined>` | handler with SessionStorage value |
3333
3434
## Methods
3535
@@ -38,14 +38,13 @@ The `useSessionStorage` function exposes the following methods:
3838
```js
3939
import { useSessionStorage } from "vue-composable";
4040

41-
const { remove, clear, setSync } = useSessionStorage(key);
41+
const { remove, clear } = useSessionStorage(key);
4242
```
4343
44-
| Signature | Description |
45-
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
46-
| `remove()` | Removes key from the SessionStorage, equivalent as `storage.value = undefined` |
47-
| `clear()` | Clears all used SessionStorage used so far |
48-
| `setSync(boolean)` | Does nothing, since the session is only available on the tab, this is here to allow the same API as `useLocalStorage`. Returns `false` |
44+
| Signature | Description |
45+
| ---------- | ------------------------------------------------------------------------------ |
46+
| `remove()` | Removes key from the SessionStorage, equivalent as `storage.value = undefined` |
47+
| `clear()` | Clears all used SessionStorage used so far |
4948
5049
## Example
5150

packages/vue-composable/__tests__/storage/localStorage.spec.ts

+55-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,57 @@ describe("localStorage", () => {
1414
consoleWarnSpy.mockClear();
1515
});
1616

17+
it("should remove value from localStorage when ref is set to undefined", async () => {
18+
const key = "test";
19+
const value = "value";
20+
const { storage } = useLocalStorage<string | undefined>(key, value);
21+
22+
await nextTick();
23+
24+
expect(storage.value).toEqual(value);
25+
expect(localStorage.getItem(key)).toEqual(value);
26+
27+
storage.value = undefined;
28+
29+
await promisedTimeout(100);
30+
31+
expect(storage.value).toEqual(undefined);
32+
expect(localStorage.getItem(key)).toEqual(null);
33+
});
34+
35+
it("should not set value in localStorage when defaultValue is undefined", async () => {
36+
const key = "test";
37+
const { storage } = useLocalStorage(key);
38+
39+
await nextTick();
40+
41+
expect(storage.value).toEqual(undefined);
42+
expect(localStorage.getItem(key)).toEqual(null);
43+
});
44+
45+
it("should handle ref value", async () => {
46+
const key = "test";
47+
const value = ref(5);
48+
const { storage } = useLocalStorage(key, value);
49+
50+
await nextTick();
51+
52+
expect(storage.value).toEqual(value.value);
53+
expect(localStorage.getItem(key)).toEqual(JSON.stringify(value.value));
54+
});
55+
56+
it("should update localStorage immediately if we\re not using debounce", async () => {
57+
const key = "test";
58+
let value = 5;
59+
const { storage } = useLocalStorage(key, value, false, false);
60+
61+
expect(localStorage.getItem(key)).toEqual(JSON.stringify(value));
62+
63+
storage.value = 10;
64+
65+
expect(localStorage.getItem(key)).toEqual(JSON.stringify(storage.value));
66+
});
67+
1768
it("should store object in localStorage if default is passed", async () => {
1869
const obj = { a: 1 };
1970
const { storage } = useLocalStorage("test", obj);
@@ -32,7 +83,7 @@ describe("localStorage", () => {
3283
expect(storage.value).toMatchObject(obj);
3384
expect(setItemSpy).toHaveBeenLastCalledWith("test", JSON.stringify(obj));
3485

35-
storage.value.a = 33;
86+
storage.value!.a = 33;
3687
await nextTick();
3788

3889
expect(storage.value).toMatchObject({ a: 33 });
@@ -48,6 +99,7 @@ describe("localStorage", () => {
4899
const { storage: storage1 } = useLocalStorage(key, { a: 1 });
49100
const { storage: storage2 } = useLocalStorage(key, { b: 1 });
50101

102+
// @ts-ignore
51103
expect(storage1.value).toMatchObject(storage2.value);
52104
});
53105

@@ -127,8 +179,8 @@ describe("localStorage", () => {
127179

128180
// key not created yet
129181
// expect(localStorage.length).toBe(1);
130-
// key doesn't exist so it's null
131-
expect(storage.value).toBeNull();
182+
// key doesn't exist so it's undefined
183+
expect(storage.value).toBeUndefined();
132184
jest.runAllTimers();
133185

134186
storage.value = { k: 0 };

packages/vue-composable/__tests__/storage/sessionStorage.spec.ts

+53-16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,55 @@ describe("sessionStorage", () => {
1414
consoleWarnSpy.mockClear();
1515
});
1616

17+
it("should remove value from sessionStorage when ref is set to undefined", async () => {
18+
const key = "test";
19+
const value = "value";
20+
const { storage } = useSessionStorage<string | undefined>(key, value);
21+
22+
expect(storage.value).toEqual(value);
23+
expect(sessionStorage.getItem(key)).toEqual(value);
24+
25+
storage.value = undefined;
26+
27+
await promisedTimeout(100);
28+
29+
expect(storage.value).toEqual(undefined);
30+
expect(sessionStorage.getItem(key)).toEqual(null);
31+
});
32+
33+
it("should not set value in sessionStorage when defaultValue is undefined", async () => {
34+
const key = "test";
35+
const { storage } = useSessionStorage(key);
36+
37+
await nextTick();
38+
39+
expect(storage.value).toEqual(undefined);
40+
expect(sessionStorage.getItem(key)).toEqual(null);
41+
});
42+
43+
it("should handle ref value", async () => {
44+
const key = "test";
45+
const value = ref(5);
46+
const { storage } = useSessionStorage(key, value);
47+
48+
await nextTick();
49+
50+
expect(storage.value).toEqual(value.value);
51+
expect(sessionStorage.getItem(key)).toEqual(JSON.stringify(value.value));
52+
});
53+
54+
it("should update sessionStorage immediately if we\re not using debounce", async () => {
55+
const key = "test";
56+
let value = 5;
57+
const { storage } = useSessionStorage(key, value, false);
58+
59+
expect(sessionStorage.getItem(key)).toEqual(JSON.stringify(value));
60+
61+
storage.value = 10;
62+
63+
expect(sessionStorage.getItem(key)).toEqual(JSON.stringify(storage.value));
64+
});
65+
1766
it("should store object in sessionStorage if default is passed", async () => {
1867
const obj = { a: 1 };
1968
const { storage } = useSessionStorage("test", obj);
@@ -32,7 +81,7 @@ describe("sessionStorage", () => {
3281
expect(storage.value).toMatchObject(obj);
3382
expect(setItemSpy).toHaveBeenLastCalledWith("test", JSON.stringify(obj));
3483

35-
storage.value.a = 33;
84+
storage.value!.a = 33;
3685
await nextTick();
3786

3887
expect(storage.value).toMatchObject({ a: 33 });
@@ -48,6 +97,7 @@ describe("sessionStorage", () => {
4897
const { storage: storage1 } = useSessionStorage(key, { a: 1 });
4998
const { storage: storage2 } = useSessionStorage(key, { b: 1 });
5099

100+
// @ts-ignore
51101
expect(storage1.value).toMatchObject(storage2.value);
52102
});
53103

@@ -93,17 +143,6 @@ describe("sessionStorage", () => {
93143
expect(storage.value).toMatchObject({ k: 1 });
94144
});
95145

96-
it("should warn if you try to sync", () => {
97-
const key = "hello";
98-
const { setSync } = useSessionStorage(key, { k: 10 });
99-
100-
expect(consoleWarnSpy).not.toHaveBeenCalled();
101-
setSync(true);
102-
expect(consoleWarnSpy).toHaveBeenCalledWith(
103-
"sync is not supported, please `useLocalStorage` instead"
104-
);
105-
});
106-
107146
it("should warn if sessionStorage is not supported", () => {
108147
setItemSpy.mockImplementationOnce(() => {
109148
throw new Error("random");
@@ -127,10 +166,8 @@ describe("sessionStorage", () => {
127166
key.value = "hey";
128167
jest.runAllTimers();
129168

130-
// key not created yet
131-
// expect(localStorage.length).toBe(1);
132-
// key doesn't exist so it's null
133-
expect(storage.value).toBeNull();
169+
// key doesn't exist so it's undefined
170+
expect(storage.value).toBeUndefined();
134171
jest.runAllTimers();
135172

136173
storage.value = { k: 0 };

packages/vue-composable/src/storage/localStorage.ts

+21-23
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,59 @@ import { RefTyped, NO_OP, unwrap } from "../utils";
33
import { useWebStorage } from "./webStorage";
44

55
export interface LocalStorageReturn<T> {
6+
/**
7+
* returns true is `localStorage` is available
8+
*/
69
supported: boolean;
710

8-
storage: Ref<T>;
11+
/**
12+
* handler with `localStorage` value
13+
*/
14+
15+
storage: Ref<T | undefined>;
916

1017
/**
11-
* @description Removes current item from the store
18+
* Removes current item from the store
1219
*/
1320
remove: () => void;
1421

1522
/**
16-
* @description Clears all tracked localStorage items
23+
* Clears all tracked `localStorage` items
1724
*/
1825
clear: () => void;
1926

2027
/**
21-
* @description Enable cross tab syncing
28+
* Enable cross tab syncing
2229
*/
2330
setSync: (sync: boolean) => void;
2431
}
2532

26-
export function useLocalStorage(
27-
key: RefTyped<string>,
28-
defaultValue?: RefTyped<string>,
29-
sync?: boolean
30-
): LocalStorageReturn<string>;
31-
export function useLocalStorage<T>(
33+
export function useLocalStorage<T = string>(
3234
key: RefTyped<string>,
3335
defaultValue?: RefTyped<T>,
34-
sync?: boolean
35-
): LocalStorageReturn<T>;
36-
export function useLocalStorage(
37-
key: RefTyped<string>,
38-
defaultValue?: RefTyped<any>,
39-
sync?: boolean
40-
) {
36+
sync = true,
37+
useDebounce = true
38+
): LocalStorageReturn<T> {
4139
const { supported, store } = useWebStorage("localStorage");
4240

4341
let remove = NO_OP;
4442
let clear = NO_OP;
4543
let setSync: LocalStorageReturn<any>["setSync"] = NO_OP;
46-
let storage = undefined;
44+
let storage = ref<T>();
4745

4846
if (supported && store) {
4947
setSync = (s) => store.setSync(unwrap(key), s);
5048
remove = () => store.removeItem(unwrap(key));
5149
clear = () => store.clear();
5250

53-
storage = store.getRef(key);
54-
if (storage.value == null) {
51+
storage = store.getRef<T>(key, useDebounce);
52+
if (storage.value === undefined) {
5553
store.save(unwrap(key), defaultValue);
56-
storage.value = defaultValue;
54+
storage.value = unwrap(defaultValue);
5755
}
5856

5957
watchEffect(() => {
60-
if (sync !== false) {
58+
if (sync) {
6159
setSync(true);
6260
}
6361
});
@@ -67,7 +65,7 @@ export function useLocalStorage(
6765
console.warn("[localStorage] is not available");
6866
}
6967

70-
storage = ref(defaultValue);
68+
storage.value = unwrap(defaultValue);
7169
}
7270

7371
return {

0 commit comments

Comments
 (0)