Skip to content

Commit 4086c5c

Browse files
authored
feat: useSyncState (#553)
1 parent 1c60cc6 commit 4086c5c

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"husky": "^8.0.3",
6464
"lint-staged": "^15.1.0",
6565
"np": "^10.0.2",
66+
"prettier": "^3.3.2",
6667
"rc-test": "^7.0.14",
6768
"react": "^18.0.0",
6869
"react-dom": "^18.0.0",

src/hooks/useSyncState.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as React from 'react';
2+
import useEvent from './useEvent';
3+
4+
type Updater<T> = T | ((prevValue: T) => T);
5+
6+
export type SetState<T> = (nextValue: Updater<T>) => void;
7+
8+
/**
9+
* Same as React.useState but will always get latest state.
10+
* This is useful when React merge multiple state updates into one.
11+
* e.g. onTransitionEnd trigger multiple event at once will be merged state update in React.
12+
*/
13+
export default function useSyncState<T>(
14+
defaultValue?: T,
15+
): [get: () => T, set: SetState<T>] {
16+
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
17+
18+
const currentValueRef = React.useRef(defaultValue);
19+
20+
const getValue = useEvent(() => {
21+
return currentValueRef.current;
22+
});
23+
24+
const setValue = useEvent((updater: Updater<T>) => {
25+
currentValueRef.current =
26+
typeof updater === 'function'
27+
? (updater as (prevValue: T) => T)(currentValueRef.current)
28+
: updater;
29+
30+
forceUpdate();
31+
});
32+
33+
return [getValue, setValue];
34+
}

tests/hooks.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import useMemo from '../src/hooks/useMemo';
77
import useMergedState from '../src/hooks/useMergedState';
88
import useMobile from '../src/hooks/useMobile';
99
import useState from '../src/hooks/useState';
10+
import useSyncState from '../src/hooks/useSyncState';
1011

1112
global.disableUseId = false;
1213

@@ -521,4 +522,22 @@ describe('hooks', () => {
521522
);
522523
});
523524
});
525+
526+
describe('useSyncState', () => {
527+
it('batch use latest', () => {
528+
const Demo = () => {
529+
const [getCounter, setCounter] = useSyncState(0);
530+
531+
React.useEffect(() => {
532+
setCounter(getCounter() + 1);
533+
setCounter(getCounter() + 1);
534+
}, [getCounter, setCounter]);
535+
536+
return getCounter();
537+
};
538+
539+
const { container } = render(<Demo />);
540+
expect(container.textContent).toEqual('2');
541+
});
542+
});
524543
});

0 commit comments

Comments
 (0)