Skip to content

Commit f539f0a

Browse files
committed
feat: new hooks useStorageValue, useLocalStorageValue
1 parent 3221a52 commit f539f0a

7 files changed

+529
-7
lines changed

Diff for: .storybook/preview.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export const parameters = {
22
layout: 'centered',
3-
viewMode: 'docs',
43
};

Diff for: README.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { useMountEffect } from "@react-hookz/web/esnext";
5151

5252
- #### Callback
5353

54-
- [`useDebounceCallback`](https://react-hookz.github.io/web/?path=/docs/lifecycle-usedebouncecallback--example)
54+
- [`useDebounceCallback`](https://react-hookz.github.io/web/?path=/docs/callback-usedebouncecallback--example)
5555
— Makes passed function debounced, otherwise acts like `useCallback`.
5656

5757
- #### Lifecycle
@@ -77,21 +77,26 @@ import { useMountEffect } from "@react-hookz/web/esnext";
7777

7878
- #### State
7979

80-
- [`useMediatedState`](https://react-hookz.github.io/web/?path=/docs/lifecycle-usemediatedstate--example)
80+
- [`useMediatedState`](https://react-hookz.github.io/web/?path=/docs/state-usemediatedstate--example)
8181
— Like `useState`, but every value set is passed through a mediator function.
82-
- [`usePrevious`](https://react-hookz.github.io/web/?path=/docs/lifecycle-useprevious--example)
82+
- [`usePrevious`](https://react-hookz.github.io/web/?path=/docs/state-useprevious--example)
8383
— Returns the value passed to the hook on previous render.
84-
- [`useSafeState`](https://react-hookz.github.io/web/?path=/docs/lifecycle-usesafestate--page)
84+
- [`useSafeState`](https://react-hookz.github.io/web/?path=/docs/state-usesafestate--page)
8585
— Like `useState`, but its state setter is guarded against sets on unmounted component.
86-
- [`useToggle`](https://react-hookz.github.io/web/?path=/docs/lifecycle-usetoggle--example)
86+
- [`useToggle`](https://react-hookz.github.io/web/?path=/docs/state-usetoggle--example)
8787
— Like `useState`, but can only become `true` or `false`.
8888

8989
- #### Navigator
9090

91-
- [`useNetworkState`](http://react-hookz.github.io/?path=/docs/sensor-usenetwork--example)
91+
- [`useNetworkState`](http://react-hookz.github.io/?path=/docs/navigator-usenetwork--example)
9292
— Tracks the state of browser's network connection.
9393

9494
- #### Miscellaneous
9595

9696
- [`useSyncedRef`](http://react-hookz.github.io/?path=/docs/miscellaneous-usesyncedref--example)
9797
— Like `useRef`, but it returns immutable ref that contains actual value.
98+
99+
- #### Web API
100+
101+
- [`useLocalStorageValue`](http://react-hookz.github.io/?path=/docs/web-api-uselocalstoragevalue--example)
102+

Diff for: src/useLocalStorageValue.ts

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { useEffect } from 'react';
2+
import { IUseStorageValueOptions, useStorageValue } from './useStorageValue';
3+
import { off, on } from './util/misc';
4+
import { isBrowser, noop } from './util/const';
5+
import { INextState } from './util/resolveHookState';
6+
7+
export type IUseLocalStorageValueOptions<
8+
T,
9+
Raw extends boolean | undefined = boolean | undefined,
10+
InitializeWithValue extends boolean | undefined = boolean | undefined
11+
> = IUseStorageValueOptions<T, Raw, InitializeWithValue> & {
12+
/**
13+
* Synchronize values between browser tabs.
14+
*
15+
* Achieved by subscribing to window's `storage` event.
16+
*
17+
* @default true
18+
*/
19+
syncTabs?: boolean;
20+
};
21+
22+
// below typings monstrosity required to provide most accurate return type hint
23+
24+
type IReturnState<
25+
T,
26+
D,
27+
O,
28+
S = O extends { raw: true } ? string : T,
29+
N = D extends null | undefined ? null | S : S,
30+
U = O extends { initializeWithStorageValue: false } ? undefined | N : N
31+
> = U;
32+
33+
type INewState<T, O, S = O extends { raw: true } ? string : T> = INextState<S>;
34+
35+
type IHookReturn<T, D, O> = [IReturnState<T, D, O>, (val: INewState<T, O>) => void, () => void];
36+
37+
export function useLocalStorageValue<T = unknown>(
38+
key: string,
39+
defaultValue?: null
40+
): IHookReturn<T, typeof defaultValue, IUseLocalStorageValueOptions<T, false, true>>;
41+
export function useLocalStorageValue<T = unknown>(
42+
key: string,
43+
defaultValue: null,
44+
options: IUseLocalStorageValueOptions<T, false | undefined>
45+
): IHookReturn<T, typeof defaultValue, typeof options>;
46+
export function useLocalStorageValue<T = unknown>(
47+
key: string,
48+
defaultValue: null,
49+
options: IUseLocalStorageValueOptions<T, false | undefined, true | undefined>
50+
): IHookReturn<T, typeof defaultValue, typeof options>;
51+
export function useLocalStorageValue<T = unknown>(
52+
key: string,
53+
defaultValue: null,
54+
options: IUseLocalStorageValueOptions<T, false | undefined, false>
55+
): IHookReturn<T, typeof defaultValue, typeof options>;
56+
export function useLocalStorageValue<T = unknown>(
57+
key: string,
58+
defaultValue: null,
59+
options: IUseLocalStorageValueOptions<T, true>
60+
): IHookReturn<T, typeof defaultValue, typeof options>;
61+
export function useLocalStorageValue<T = unknown>(
62+
key: string,
63+
defaultValue: null,
64+
options: IUseLocalStorageValueOptions<T, true, true | undefined>
65+
): IHookReturn<T, typeof defaultValue, typeof options>;
66+
export function useLocalStorageValue<T = unknown>(
67+
key: string,
68+
defaultValue: null,
69+
options: IUseLocalStorageValueOptions<T, true, false>
70+
): IHookReturn<T, typeof defaultValue, typeof options>;
71+
72+
export function useLocalStorageValue<T>(
73+
key: string,
74+
defaultValue: T
75+
): IHookReturn<T, typeof defaultValue, IUseLocalStorageValueOptions<T, false, true>>;
76+
77+
export function useLocalStorageValue<T = unknown>(
78+
key: string,
79+
defaultValue: null,
80+
options: IUseLocalStorageValueOptions<T, false | undefined>
81+
): IHookReturn<T, typeof defaultValue, typeof options>;
82+
export function useLocalStorageValue<T = unknown>(
83+
key: string,
84+
defaultValue: T,
85+
options: IUseLocalStorageValueOptions<T, false | undefined, true | undefined>
86+
): IHookReturn<T, typeof defaultValue, typeof options>;
87+
export function useLocalStorageValue<T = unknown>(
88+
key: string,
89+
defaultValue: T,
90+
options: IUseLocalStorageValueOptions<T, false | undefined, false>
91+
): IHookReturn<T, typeof defaultValue, typeof options>;
92+
export function useLocalStorageValue<T = unknown>(
93+
key: string,
94+
defaultValue: T,
95+
options: IUseLocalStorageValueOptions<T, true>
96+
): IHookReturn<T, typeof defaultValue, typeof options>;
97+
export function useLocalStorageValue<T = unknown>(
98+
key: string,
99+
defaultValue: T,
100+
options: IUseLocalStorageValueOptions<T, true, true | undefined>
101+
): IHookReturn<T, typeof defaultValue, typeof options>;
102+
export function useLocalStorageValue<T = unknown>(
103+
key: string,
104+
defaultValue: T,
105+
options: IUseLocalStorageValueOptions<T, true, false>
106+
): IHookReturn<T, typeof defaultValue, typeof options>;
107+
108+
export function useLocalStorageValue<T>(
109+
key: string,
110+
defaultValue?: T | null,
111+
options?: IUseLocalStorageValueOptions<T>
112+
): IHookReturn<T, typeof defaultValue, typeof options>;
113+
114+
/**
115+
* Manages a single LocalStorage key.
116+
*
117+
* @param key LocalStorage key to manage
118+
* @param defaultValue Default value to return in case key not presented in LocalStorage
119+
* @param options
120+
*/
121+
export function useLocalStorageValue<T>(
122+
key: string,
123+
defaultValue: T | null = null,
124+
options: IUseLocalStorageValueOptions<T> = {}
125+
): IHookReturn<T, typeof defaultValue, typeof options> {
126+
const { syncTabs = true, ...storageOptions } = options;
127+
const [value, setValue, removeValue, fetchValue] = useStorageValue(
128+
localStorage,
129+
key,
130+
defaultValue,
131+
storageOptions
132+
);
133+
134+
useEffect(() => {
135+
if (!isBrowser || !syncTabs) return noop;
136+
137+
on(window, 'storage', fetchValue, { passive: true });
138+
139+
return () => {
140+
off(window, 'storage', fetchValue);
141+
};
142+
// eslint-disable-next-line react-hooks/exhaustive-deps
143+
}, [syncTabs]);
144+
145+
return [value, setValue, removeValue];
146+
}

0 commit comments

Comments
 (0)