Skip to content

Commit

Permalink
feat: useSyncState (#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
zombieJ committed Jun 17, 2024
1 parent 1c60cc6 commit 4086c5c
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
"np": "^10.0.2",
"prettier": "^3.3.2",
"rc-test": "^7.0.14",
"react": "^18.0.0",
"react-dom": "^18.0.0",
Expand Down
34 changes: 34 additions & 0 deletions src/hooks/useSyncState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import useEvent from './useEvent';

type Updater<T> = T | ((prevValue: T) => T);

export type SetState<T> = (nextValue: Updater<T>) => void;

/**
* Same as React.useState but will always get latest state.
* This is useful when React merge multiple state updates into one.
* e.g. onTransitionEnd trigger multiple event at once will be merged state update in React.
*/
export default function useSyncState<T>(
defaultValue?: T,
): [get: () => T, set: SetState<T>] {
const [, forceUpdate] = React.useReducer(x => x + 1, 0);

const currentValueRef = React.useRef(defaultValue);

const getValue = useEvent(() => {
return currentValueRef.current;
});

const setValue = useEvent((updater: Updater<T>) => {
currentValueRef.current =
typeof updater === 'function'
? (updater as (prevValue: T) => T)(currentValueRef.current)
: updater;

forceUpdate();
});

return [getValue, setValue];
}
19 changes: 19 additions & 0 deletions tests/hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useMemo from '../src/hooks/useMemo';
import useMergedState from '../src/hooks/useMergedState';
import useMobile from '../src/hooks/useMobile';
import useState from '../src/hooks/useState';
import useSyncState from '../src/hooks/useSyncState';

global.disableUseId = false;

Expand Down Expand Up @@ -521,4 +522,22 @@ describe('hooks', () => {
);
});
});

describe('useSyncState', () => {
it('batch use latest', () => {
const Demo = () => {
const [getCounter, setCounter] = useSyncState(0);

React.useEffect(() => {
setCounter(getCounter() + 1);
setCounter(getCounter() + 1);
}, [getCounter, setCounter]);

return getCounter();
};

const { container } = render(<Demo />);
expect(container.textContent).toEqual('2');
});
});
});

0 comments on commit 4086c5c

Please sign in to comment.