Skip to content

Commit

Permalink
feat: sync storage state
Browse files Browse the repository at this point in the history
  • Loading branch information
vaakian committed Aug 19, 2023
1 parent 2ff3ed9 commit 5fbdefd
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 4 deletions.
16 changes: 16 additions & 0 deletions packages/hooks/src/createUseStorageState/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useState } from 'react';
import useEventListener from '../useEventListener';
import useMemoizedFn from '../useMemoizedFn';
import useUpdateEffect from '../useUpdateEffect';
import { isFunction, isUndef } from '../utils';

const SYNC_STORAGE_EVENT = 'AHOOKS_SYNC_STORAGE_EVENT';

export type SetState<S> = S | ((prevState?: S) => S);

export interface Options<T> {
Expand Down Expand Up @@ -76,8 +79,21 @@ export function createUseStorageState(getStorage: () => Storage | undefined) {
console.error(e);
}
}
dispatchEvent(new StorageEvent(SYNC_STORAGE_EVENT, { key }));
};

const syncStorageState = (event: StorageEvent) => {
if (event.key === key) {
setState(getStoredValue());
}
};

// from different tabs
useEventListener('storage', syncStorageState);

// from the same tab but different hooks
useEventListener(SYNC_STORAGE_EVENT, syncStorageState);

return [state, useMemoizedFn(updateState)] as const;
}
return useStorageState;
Expand Down
5 changes: 5 additions & 0 deletions packages/hooks/src/useEventListener/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ function useEventListener<K extends keyof WindowEventMap>(
handler: (ev: WindowEventMap[K]) => void,
options?: Options<Window>,
): void;
function useEventListener(
eventName: string,
handler: (event: Event) => void,
options?: Options,
): void;
function useEventListener(eventName: string, handler: noop, options: Options): void;

function useEventListener(eventName: string, handler: noop, options: Options = {}) {
Expand Down
20 changes: 16 additions & 4 deletions packages/hooks/src/useLocalStorageState/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('useLocalStorageState', () => {
anotherHook.result.current.setState('C');
});
expect(anotherHook.result.current.state).toBe('C');
expect(hook.result.current.state).toBe('B');
expect(hook.result.current.state).toBe('C');
});

it('should support object', () => {
Expand All @@ -48,7 +48,7 @@ describe('useLocalStorageState', () => {
});
});
expect(anotherHook.result.current.state).toEqual({ name: 'C' });
expect(hook.result.current.state).toEqual({ name: 'B' });
expect(hook.result.current.state).toEqual({ name: 'C' });
});

it('should support number', () => {
Expand All @@ -65,7 +65,7 @@ describe('useLocalStorageState', () => {
anotherHook.result.current.setState(3);
});
expect(anotherHook.result.current.state).toBe(3);
expect(hook.result.current.state).toBe(2);
expect(hook.result.current.state).toBe(3);
});

it('should support boolean', () => {
Expand All @@ -82,7 +82,7 @@ describe('useLocalStorageState', () => {
anotherHook.result.current.setState(true);
});
expect(anotherHook.result.current.state).toBe(true);
expect(hook.result.current.state).toBe(false);
expect(hook.result.current.state).toBe(true);
});

it('should support null', () => {
Expand All @@ -106,4 +106,16 @@ describe('useLocalStorageState', () => {
});
expect(hook.result.current.state).toBe('hello world, zhangsan');
});

it('should sync state when changes', async () => {
const LOCAL_STORAGE_KEY = 'test-sync-state';
const hook = setUp(LOCAL_STORAGE_KEY, 'foo');
const anotherHook = setUp(LOCAL_STORAGE_KEY, 'foo');
expect(hook.result.current.state).toBe('foo');
act(() => {
hook.result.current.setState('bar');
});
expect(hook.result.current.state).toBe('bar');
expect(anotherHook.result.current.state).toBe('bar');
});
});
33 changes: 33 additions & 0 deletions packages/hooks/src/useLocalStorageState/demo/demo4.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* title: Sync state with localStorage
* desc: The state can be synchronized with `localStorage`, ensuring that it remains consistent across different tabs or when modified elsewhere.(try to open this demo in two tabs, and then click the button in one of the tabs)
*
* title.zh-CN: 将 state 与 localStorage 保持同步
* desc.zh-CN: 当 state 在其他地方被修改时,会自动同步到 `localStorage` 中,包括不同浏览器 tab 之间。(尝试打开两个此页面,点击其中一个页面的按钮,另一个页面的 count 会自动更新)
*/

import React from 'react';
import { useLocalStorageState } from 'ahooks';

export default function () {
return (
<>
<Counter />
<Counter />
</>
);
}

function Counter() {
const [count, setCount] = useLocalStorageState('use-local-storage-state-demo4', {
defaultValue: 0,
});

const add = () => setCount(count! + 1);

return (
<button onClick={add} style={{ margin: '0 8px' }}>
count: {count}
</button>
);
}
4 changes: 4 additions & 0 deletions packages/hooks/src/useLocalStorageState/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ A Hook that store state into localStorage.

<code src="./demo/demo3.tsx" />

### Sync state with localStorage

<code src="./demo/demo4.tsx" />

## API

If you want to delete this record from localStorage, you can use `setState()` or `setState(undefined)`.
Expand Down
4 changes: 4 additions & 0 deletions packages/hooks/src/useLocalStorageState/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ nav:

<code src="./demo/demo3.tsx" />

### 将 state 与 localStorage 保持同步

<code src="./demo/demo4.tsx" />

## API

如果想从 localStorage 中删除这条数据,可以使用 `setState()``setState(undefined)`
Expand Down

0 comments on commit 5fbdefd

Please sign in to comment.