Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework useStorageValue to more simple and robust variant #960

Merged
merged 22 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
322678d
feat: rework useStorageValue to more simple and robust variant
xobotyi Oct 10, 2022
33222db
fix: make value fetched only during first render
xobotyi Oct 11, 2022
cc54df0
refactor(useSessionStorage): Fix wrongly named constant
ArttuOll Oct 19, 2022
19e9912
docs(useSessionStorageValue): Write documentation for the new API
ArttuOll Oct 19, 2022
b3ae86a
docs(useLocalStorage): Write documentation for the new API
ArttuOll Oct 19, 2022
3cf5a1c
docs(useStorageValue): Fix wrong default value for initializeWithValu…
ArttuOll Oct 19, 2022
31a3a27
chore(release): 16.0.0 [skip ci]
semantic-release-bot Oct 9, 2022
24203f7
docs(contributor): contrib-readme-action has updated readme
github-actions[bot] Oct 9, 2022
ca2886b
chore(deps-dev): bump jest-environment-jsdom from 29.1.2 to 29.2.0 (#…
dependabot[bot] Oct 17, 2022
d93d0e7
chore(deps-dev): bump jest from 29.1.2 to 29.2.0 (#966)
dependabot[bot] Oct 17, 2022
4189dd6
docs(Add example for useDeepCompareEffect): Added missing example for…
ArttuOll Oct 18, 2022
ff5804a
docs(contributor): contrib-readme-action has updated readme
github-actions[bot] Oct 18, 2022
e510b8c
chore(deps-dev): bump @react-hookz/eslint-config from 1.7.3 to 1.7.4 …
dependabot[bot] Oct 19, 2022
c5c52df
chore(deps-dev): bump jest and @types/jest (#968)
dependabot[bot] Oct 19, 2022
b65db7e
chore(deps-dev): bump jest-environment-jsdom from 29.2.0 to 29.2.1 (#…
dependabot[bot] Oct 19, 2022
ec20f41
chore(deps-dev): bump @storybook/storybook-deployer (#971)
dependabot[bot] Oct 20, 2022
3d393f4
chore(deps-dev): bump @babel/core from 7.19.3 to 7.19.6 (#972)
dependabot[bot] Oct 21, 2022
a3f45ea
docs: add `useWindowSize` to migration guide (#973)
lensbart Oct 22, 2022
de13428
docs(contributor): contrib-readme-action has updated readme
github-actions[bot] Oct 22, 2022
6a17640
Merge branch 'ArttuOll_rework-useStorageValue' into rework-useStorage…
xobotyi Oct 22, 2022
e5b56a2
fix: actualize description a bit
xobotyi Oct 22, 2022
b5b776e
Merge branch 'master' into rework-useStorageValue
xobotyi Oct 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 7 additions & 17 deletions src/useLocalStorageValue/__docs__/example.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,15 @@ interface ExampleProps {
* LocalStorage key to manage.
*/
key: string;
/**
* Subscribe to window's `storage` event.
*/
handleStorageEvent: boolean;
/**
* Isolate hook from others on page - it will not receive updates from other hooks with the same key.
*/
isolated: boolean;
}

export const Example: React.FC<ExampleProps> = ({
key = 'react-hookz-ls-test',
defaultValue = '@react-hookz is awesome',
handleStorageEvent = true,
isolated = false,
}) => {
const [value, setValue, removeValue] = useLocalStorageValue(key, defaultValue, {
handleStorageEvent,
isolated,
const lsVal = useLocalStorageValue(key, {
defaultValue,
initializeWithValue: true,
});

return (
Expand All @@ -40,12 +30,12 @@ export const Example: React.FC<ExampleProps> = ({
<br />
<input
type="text"
value={value}
value={lsVal.value}
onChange={(ev) => {
setValue(ev.currentTarget.value);
lsVal.set(ev.currentTarget.value);
}}
/>{' '}
<button onClick={removeValue}>clear storage value</button>
/>
<button onClick={lsVal.remove}>remove storage value</button>
</div>
);
};
40 changes: 18 additions & 22 deletions src/useLocalStorageValue/__docs__/story.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Example } from './example.stories';
import { ImportPath } from '../../__docs__/ImportPath';
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
import { Example } from './example.stories'
import { ImportPath } from '../../__docs__/ImportPath'
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs'

<Meta title="Side-effect/useLocalStorageValue" component={Example} />

Expand All @@ -13,16 +13,13 @@ Manages a single LocalStorage key.
- Synchronized between all hooks on the page with the same key.
- SSR compatible.

> **_This hook provides stable API, meaning returned methods does not change between renders_**

> This hook uses `useSafeState` underneath, so it is safe to use its `setState` in async hooks.
> **_This hook provides stable API, meaning the returned methods do not change between renders._**

> Does not allow usage of `null` value, since JSON allows serializing `null` values - it would be
> impossible to separate null value fom 'no such value' API result which is also `null`.

> While using SSR, to avoid hydration mismatch, consider setting `initializeWithStorageValue` option
> to `false`, this will yield `undefined` state on first render and defer value fetch till effects
> execution stage.
> Due to support of SSR this hook returns undefined on first render even if value is there, to avoid
> this behavior set the `initializeWithValue` option to true.

#### Example

Expand Down Expand Up @@ -50,23 +47,22 @@ function useLocalStorageValue<T>(

<ImportPath />


#### Arguments

- **key** _`string`_ - LocalStorage key to manage.
- **defaultValue** _`T | null`_ _(default: null)_ - Default value to return in case key not
presented in LocalStorage.
- **options** _`object`_ - Hook options:
- **isolated** _`boolean`_ _(default: false)_ - Disable synchronisation with other hook instances
with the same key on the same page.
- **handleStorageEvent** _`boolean`_ _(default: true)_ - Subscribe to window's `storage` event.
- **storeDefaultValue** _`boolean`_ _(default: false)_ - store default value.
- **initializeWithStorageValue** _`boolean`_ _(default: true)_ - fetch storage value on first
render. If set to `false` will make hook to yield `undefined` state on first render and defer
value fetch till effects execution stage.
- **defaultValue** _`T | null`_ - Value to return if `key` is not present in LocalStorage.
- **initializeWithValue** _`boolean`_ _(default: true)_ - Fetch storage value on first render. If
set to `false` will make the hook yield `undefined` on first render and defer fetching of the
value until effects are executed.

#### Return

0. **state** - LocalStorage item value or default value in case of item absence.
1. **setValue** - Method to set new item value.
2. **removeValue** - Method to remove item from storage.
3. **fetchValue** - Method to pull value from localStorage.
Object with following properties. Note that this object changes with value while its methods are
stable between renders, thus it is safe to pass them as props.
- **value** - LocalStorage value of the given `key` argument or `defaultValue`, if the key was not
present.
- **set** - Method to set a new value for the managed `key`.
- **remove** - Method to remove the current value of `key`.
- **fetch** - Method to manually retrieve the value of `key`.
76 changes: 24 additions & 52 deletions src/useLocalStorageValue/useLocalStorageValue.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
HookReturn,
UseStorageValueOptions,
useStorageValue,
UseStorageValueOptions,
UseStorageValueResult,
} from '../useStorageValue/useStorageValue';
import { isBrowser, noop } from '../util/const';

let IS_LOCAL_STORAGE_AVAILABLE = false;
let IS_LOCAL_STORAGE_AVAILABLE: boolean;

try {
IS_LOCAL_STORAGE_AVAILABLE = isBrowser && !!window.localStorage;
Expand All @@ -15,62 +15,34 @@ try {
IS_LOCAL_STORAGE_AVAILABLE = false;
}

interface UseLocalStorageValue {
<T = unknown>(key: string, defaultValue?: null, options?: UseStorageValueOptions): HookReturn<
T,
typeof defaultValue,
UseStorageValueOptions<true | undefined>
>;

<T = unknown>(
key: string,
defaultValue: null,
options: UseStorageValueOptions<false>
): HookReturn<T, typeof defaultValue, typeof options>;

<T>(key: string, defaultValue: T, options?: UseStorageValueOptions): HookReturn<
T,
typeof defaultValue,
UseStorageValueOptions<true | undefined>
>;

<T>(key: string, defaultValue: T, options: UseStorageValueOptions<false>): HookReturn<
T,
typeof defaultValue,
typeof options
>;

<T>(key: string, defaultValue?: T | null, options?: UseStorageValueOptions): HookReturn<
T,
typeof defaultValue,
typeof options
>;
}
type UseLocalStorageValue = <
Type,
Default extends Type = Type,
Initialize extends boolean | undefined = boolean | undefined
>(
key: string,
options?: UseStorageValueOptions<Type, Initialize>
) => UseStorageValueResult<Type, Default, Initialize>;

/**
* Manages a single localStorage key.
*
* @param key Storage key to manage
* @param defaultValue Default value to yield in case the key is not in storage
* @param options
*/
export const useLocalStorageValue: UseLocalStorageValue = IS_LOCAL_STORAGE_AVAILABLE
? <T>(
key: string,
defaultValue: T | null = null,
options: UseStorageValueOptions = {}
): HookReturn<T, typeof defaultValue, typeof options> =>
useStorageValue(localStorage, key, defaultValue, options)
: <T>(
key: string,
defaultValue: T | null = null,
options: UseStorageValueOptions = {}
): HookReturn<T, typeof defaultValue, typeof options> => {
/* istanbul ignore next */
export const useLocalStorageValue: UseLocalStorageValue = !IS_LOCAL_STORAGE_AVAILABLE
? <
Type,
Default extends Type = Type,
Initialize extends boolean | undefined = boolean | undefined
>(
_key: string,
_options?: UseStorageValueOptions<Type, Initialize>
): UseStorageValueResult<Type, Default, Initialize> => {
if (isBrowser && process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.warn('LocalStorage is not available in this environment');
}

return [undefined, noop, noop, noop];
return { value: undefined as Type, set: noop, remove: noop, fetch: noop };
}
: (key, options) => {
return useStorageValue(localStorage, key, options);
};
23 changes: 5 additions & 18 deletions src/useSessionStorageValue/__docs__/example.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,13 @@ interface ExampleProps {
* SessionStorage key to manage.
*/
key: string;
/**
* Subscribe to window's `storage` event.
*/
handleStorageEvent: boolean;
/**
* Isolate hook from others on page - it will not receive updates from other hooks with the same key.
*/
isolated: boolean;
}

export const Example: React.FC<ExampleProps> = ({
key = 'react-hookz-ss-test',
defaultValue = '@react-hookz is awesome',
handleStorageEvent = true,
isolated = false,
}) => {
const [value, setValue, removeValue] = useSessionStorageValue(key, defaultValue, {
handleStorageEvent,
isolated,
});
const ssVal = useSessionStorageValue(key, { defaultValue, initializeWithValue: true });

return (
<div>
Expand All @@ -40,12 +27,12 @@ export const Example: React.FC<ExampleProps> = ({
<br />
<input
type="text"
value={value}
value={ssVal.value}
onChange={(ev) => {
setValue(ev.currentTarget.value);
ssVal.set(ev.currentTarget.value);
}}
/>{' '}
<button onClick={removeValue}>clear storage value</button>
/>
<button onClick={ssVal.remove}>remove storage value</button>
</div>
);
};
39 changes: 17 additions & 22 deletions src/useSessionStorageValue/__docs__/story.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Example } from './example.stories';
import { ImportPath } from '../../__docs__/ImportPath';
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
import { Example } from './example.stories'
import { ImportPath } from '../../__docs__/ImportPath'
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs'

<Meta title="Side-effect/useSessionStorageValue" component={Example} />

Expand All @@ -13,16 +13,13 @@ Manages a single SessionStorage key.
- Synchronized between all hooks on the page with the same key.
- SSR compatible.

> **_This hook provides stable API, meaning returned methods does not change between renders_**

> This hook uses `useSafeState` underneath, so it is safe to use its `setState` in async hooks.
> **_This hook provides stable API, meaning the returned methods do not change between renders._**

> Does not allow usage of `null` value, since JSON allows serializing `null` values - it would be
> impossible to separate null value fom 'no such value' API result which is also `null`.

> While using SSR, to avoid hydration mismatch, consider setting `initializeWithStorageValue` option
> to `false`, this will yield `undefined` state on first render and defer value fetch till effects
> execution stage.
> Due to support of SSR this hook returns undefined on first render even if value is there, to avoid
> this behavior set the `initializeWithValue` option to true.

#### Example

Expand Down Expand Up @@ -53,20 +50,18 @@ function useSessionStorageValue<T>(
#### Arguments

- **key** _`string`_ - SessionStorage key to manage.
- **defaultValue** _`T | null`_ _(default: null)_ - Default value to return in case key not
presented in SessionStorage.
- **options** _`object`_ - Hook options:
- **isolated** _`boolean`_ _(default: false)_ - Disable synchronisation with other hook instances
with the same key on same page.
- **handleStorageEvent** _`boolean`_ _(default: true)_ - Subscribe to window's `storage` event.
- **storeDefaultValue** _`boolean`_ _(default: false)_ - store default value.
- **initializeWithStorageValue** _`boolean`_ _(default: true)_ - fetch storage value on first
render. If set to `false` will make hook to yield `undefined` state on first render and defer
value fetch till effects execution stage.
- **defaultValue** _`T | null`_ - Value to return if `key` is not present in SessionStorage.
- **initializeWithValue** _`boolean`_ _(default: true)_ - Fetch storage value on first render. If
set to `false` will make the hook yield `undefined` on first render and defer fetching of the
value until effects are executed.

#### Return

0. **state** - SessionStorage item value or default value in case of item absence.
1. **setValue** - Method to set new item value.
2. **removeValue** - Method to remove item from storage.
3. **fetchValue** - Method to pull value from sessionStorage.
Object with following properties. Note that this object changes with value while its methods are
stable between renders, thus it is safe to pass them as props.
- **value** - SessionStorage value of the given `key` argument or `defaultValue`, if the key was not
present.
- **set** - Method to set a new value for the managed `key`.
- **remove** - Method to remove the current value of `key`.
- **fetch** - Method to manually retrieve the value of `key`.
Loading