From b1708038ecd462c6950fdd56efbf408532ef9f39 Mon Sep 17 00:00:00 2001 From: Tmk Date: Sun, 3 Apr 2022 19:15:05 +0800 Subject: [PATCH 1/3] fix: reset state when unmounted --- src/hooks/useIsFirstRender.ts | 12 ++++++++++++ src/hooks/useMergedState.ts | 6 +++--- src/hooks/useState.ts | 21 ++++++++++++--------- 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 src/hooks/useIsFirstRender.ts diff --git a/src/hooks/useIsFirstRender.ts b/src/hooks/useIsFirstRender.ts new file mode 100644 index 00000000..f68f531c --- /dev/null +++ b/src/hooks/useIsFirstRender.ts @@ -0,0 +1,12 @@ +import * as React from 'react'; + +export default function useIsFirstRender(): boolean { + const firstRenderRef = React.useRef(true); + React.useEffect(() => { + firstRenderRef.current = false; + return () => { + firstRenderRef.current = true; + }; + }, []); + return firstRenderRef.current; +} diff --git a/src/hooks/useMergedState.ts b/src/hooks/useMergedState.ts index fc5a098b..a9b62319 100644 --- a/src/hooks/useMergedState.ts +++ b/src/hooks/useMergedState.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import useState from './useState'; +import useIsFirstRender from './useIsFirstRender'; /** * Similar to `useState` but will use props value if provided. @@ -49,10 +50,9 @@ export default function useControlledState( ); // Effect of reset value to `undefined` - const firstRenderRef = React.useRef(true); + const isFirstRender = useIsFirstRender(); React.useEffect(() => { - if (firstRenderRef.current) { - firstRenderRef.current = false; + if (isFirstRender) { return; } diff --git a/src/hooks/useState.ts b/src/hooks/useState.ts index d08cdbef..7494b725 100644 --- a/src/hooks/useState.ts +++ b/src/hooks/useState.ts @@ -8,11 +8,11 @@ export type SetState = ( * Will not update state when destroyed. * Developer should make sure this is safe to ignore. */ - ignoreDestroy?: boolean, + ignoreSetStateIfDestroyed?: boolean, ) => void; /** - * Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed. + * Same as React.useState but `setState` accept `ignoreSetStateIfDestroyed` param to not to setState after destroyed. * We do not make this auto is to avoid real memory leak. * Developer should confirm it's safe to ignore themselves. */ @@ -22,15 +22,18 @@ export default function useState( const destroyRef = React.useRef(false); const [value, setValue] = React.useState(defaultValue); - React.useEffect( - () => () => { + React.useEffect(() => { + destroyRef.current = false; + return () => { destroyRef.current = true; - }, - [], - ); + }; + }, []); - function safeSetState(updater: Updater, ignoreDestroy?: boolean) { - if (ignoreDestroy && destroyRef.current) { + function safeSetState( + updater: Updater, + ignoreSetStateIfDestroyed?: boolean, + ) { + if (ignoreSetStateIfDestroyed && destroyRef.current) { return; } From bdbd0b3fc05b2dab832c6ae12d8caddb765c0ef5 Mon Sep 17 00:00:00 2001 From: Tmk Date: Sun, 3 Apr 2022 19:53:06 +0800 Subject: [PATCH 2/3] test: add useIsFirstRender tests --- tests/hooks.test.js | 21 +++++++++++++++++++++ tests/scrollLocker.test.ts | 2 +- tests/switchScrollingEffect.test.ts | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/hooks.test.js b/tests/hooks.test.js index 0a769388..eeda44d7 100644 --- a/tests/hooks.test.js +++ b/tests/hooks.test.js @@ -4,6 +4,7 @@ import useMemo from '../src/hooks/useMemo'; import useMergedState from '../src/hooks/useMergedState'; import useLayoutEffect from '../src/hooks/useLayoutEffect'; import useState from '../src/hooks/useState'; +import useIsFirstRender from '../src/hooks/useIsFirstRender'; describe('hooks', () => { it('useMemo', () => { @@ -177,4 +178,24 @@ describe('hooks', () => { }, 50); }); }); + + it('useIsFirstRender', () => { + const Demo = () => { + const isFirstRender = useIsFirstRender(); + const [, update] = useState(); + return ( + <> +
{String(isFirstRender)}
+