From b4a39dcb3f370ade3fbcd4bde45e73ead5c177c7 Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:27:59 +0000 Subject: [PATCH 01/10] feat: add useComponentWillReceiveUpdate --- .../use-component-will-receive-update.mdx | 62 +++++++++++++++++++ .../index.ts | 52 ++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 docs/src/pages/use-component-will-receive-update.mdx create mode 100644 src/use-component-will-receive-update/index.ts diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx new file mode 100644 index 00000000..a2625362 --- /dev/null +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -0,0 +1,62 @@ +--- +title: useComponentWillReceiveUpdate +--- + +# useComponentWillReceiveUpdate + +import ExportMetaInfo from '../components/export-meta-info'; + + + +Reset part of states when props changed. + +## Usage + +```js +import { useComponentWillReceiveUpdate } from 'foxact/use-abortable-effect'; + +function Component(props) { + const [a, setA] = useState('') + const [b, setB] = useState('') + // when props.x changed, only reset a, not b + useComponentWillReceiveUpdate(() => { + setA('') + }, [props.x]); +} +``` + +If you're using useEffect like this: + +```js + const [state, setState] = useState(false) + useEffect(() => setState(false), [props.someProp]) +``` + +Don't do it. See [Adjusting some state when a prop changes](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) +It should be like this: +```js + const [prev, setPrev] = useState(state) + if (prev !== state) { + setPrev(state) + setState(false) + } +``` + +This hook is a helper for the above pattern. + +```js +useComponentWillReceiveUpdate(() => setState(false), [state]) +``` + +This only applies to states of the current component. +Modifying states from other components causes React reporting errors. +You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) +and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). +If you really need to edit other components' states, write it like this: + +```js +useComponentWillReceiveUpdate(() => { + setLocalState(false) + Promise.resolve().then(() => props.setParentState(false)) +}, [state]) +``` \ No newline at end of file diff --git a/src/use-component-will-receive-update/index.ts b/src/use-component-will-receive-update/index.ts new file mode 100644 index 00000000..6644d531 --- /dev/null +++ b/src/use-component-will-receive-update/index.ts @@ -0,0 +1,52 @@ +import { useState } from 'react'; + +/** + * If you're using useEffect like this: + * + * ```js + * const [state, setState] = useState(false) + * useEffect(() => setState(false), [props.someProp]) + * ``` + * Don't do it. See [Adjusting some state when a prop changes](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) + * It should be like this: + * ```js + * const [prev, setPrev] = useState(state) + * if (prev !== state) { + * setPrev(state) + * setState(false) + * } + * ``` + * This hook is a helper for the above pattern. + * ```js + * useComponentWillReceiveUpdate(() => setState(false), [state]) + * ``` + * + * This only applies to states of the current component. + * Modifying states from other components causes React reporting errors. + * You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) + * and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). + * If you really need to edit other components' states, write it like this: + * + * ```js + * useComponentWillReceiveUpdate(() => { + * setLocalState(false) + * Promise.resolve().then(() => props.setParentState(false)) + * }, [state]) + * ``` + * + * @param callback + * @param deps + */ +export function useComponentWillReceiveUpdate(callback: () => void, deps: readonly unknown[]) { + deps = [...deps] + const [prev, setPrev] = useState(deps) + let changed = deps.length !== prev.length + for (let i = 0; i < deps.length; i += 1) { + if (changed) break + if (prev[i] !== deps[i]) changed = true + } + if (changed) { + setPrev(deps) + callback() + } +} From f9349e0cc9bd0a8a03206d9ca6f1ae5d104ac9ef Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:31:32 +0000 Subject: [PATCH 02/10] fix: lint --- .../index.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/use-component-will-receive-update/index.ts b/src/use-component-will-receive-update/index.ts index 6644d531..c8bd3d00 100644 --- a/src/use-component-will-receive-update/index.ts +++ b/src/use-component-will-receive-update/index.ts @@ -38,15 +38,15 @@ import { useState } from 'react'; * @param deps */ export function useComponentWillReceiveUpdate(callback: () => void, deps: readonly unknown[]) { - deps = [...deps] - const [prev, setPrev] = useState(deps) - let changed = deps.length !== prev.length - for (let i = 0; i < deps.length; i += 1) { - if (changed) break - if (prev[i] !== deps[i]) changed = true - } - if (changed) { - setPrev(deps) - callback() - } + deps = [...deps]; + const [prev, setPrev] = useState(deps); + let changed = deps.length !== prev.length; + for (let i = 0; i < deps.length; i += 1) { + if (changed) break; + if (prev[i] !== deps[i]) changed = true; + } + if (changed) { + setPrev(deps); + callback(); + } } From c03f897d14adb7dfc25b6b949d9d9b8f4e9a2775 Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Thu, 10 Oct 2024 23:45:32 +0900 Subject: [PATCH 03/10] Update use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index a2625362..db6fa636 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -32,7 +32,7 @@ If you're using useEffect like this: useEffect(() => setState(false), [props.someProp]) ``` -Don't do it. See [Adjusting some state when a prop changes](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) +Don't do it. See [Adjusting some state when a prop changes](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) or [Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders) It should be like this: ```js const [prev, setPrev] = useState(state) @@ -50,13 +50,12 @@ useComponentWillReceiveUpdate(() => setState(false), [state]) This only applies to states of the current component. Modifying states from other components causes React reporting errors. -You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) -and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). -If you really need to edit other components' states, write it like this: +You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). +If you need to edit other components' states, write it like this: ```js useComponentWillReceiveUpdate(() => { setLocalState(false) Promise.resolve().then(() => props.setParentState(false)) }, [state]) -``` \ No newline at end of file +``` From 8e4fbb912d29646c7df2542d7d95293dd669ac53 Mon Sep 17 00:00:00 2001 From: Sukka Date: Thu, 10 Oct 2024 23:11:16 +0800 Subject: [PATCH 04/10] Update docs/src/pages/use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index db6fa636..f92d7d6a 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -8,7 +8,7 @@ import ExportMetaInfo from '../components/export-meta-info'; -Reset part of states when props changed. +Change states based on changed props during a re-render. The name of the hook comes from [`UNSAFE_componentWillReceiveProps` method of class components](https://react.dev/reference/react/Component#unsafe_componentwillreceiveprops). ## Usage From 1eb040a7dbf4716a19da41142815a1f9d93a0196 Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Fri, 11 Oct 2024 00:24:02 +0900 Subject: [PATCH 05/10] Update use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index f92d7d6a..e5fc2f5c 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -35,11 +35,11 @@ If you're using useEffect like this: Don't do it. See [Adjusting some state when a prop changes](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) or [Storing information from previous renders](https://react.dev/reference/react/useState#storing-information-from-previous-renders) It should be like this: ```js - const [prev, setPrev] = useState(state) - if (prev !== state) { - setPrev(state) - setState(false) - } +const [prev, setPrev] = useState(state) +if (prev !== state) { + setPrev(state) + setState(false) +} ``` This hook is a helper for the above pattern. @@ -56,6 +56,6 @@ If you need to edit other components' states, write it like this: ```js useComponentWillReceiveUpdate(() => { setLocalState(false) - Promise.resolve().then(() => props.setParentState(false)) + flushSync(() => props.setParentState(false)) }, [state]) ``` From a3e7931f6ab8afd294f3c6477b289fb1bda90360 Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Fri, 11 Oct 2024 00:26:29 +0900 Subject: [PATCH 06/10] Update use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index e5fc2f5c..3dc0d167 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -55,7 +55,7 @@ If you need to edit other components' states, write it like this: ```js useComponentWillReceiveUpdate(() => { - setLocalState(false) - flushSync(() => props.setParentState(false)) + flushSync(() => setLocalState(false)) + props.setParentState(false) }, [state]) ``` From e1f8874ba4c1d577ebc0a68b42a981b4b38dcf47 Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Fri, 11 Oct 2024 02:35:32 +0900 Subject: [PATCH 07/10] Update use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index 3dc0d167..774429fc 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -55,7 +55,11 @@ If you need to edit other components' states, write it like this: ```js useComponentWillReceiveUpdate(() => { + // Force React to immediately flush scheduled/batched updates flushSync(() => setLocalState(false)) + // By the time flushSync finishes and reaches this line, the DOM update + // for local state change has finished, you can now safely trigger parent + // components' re-render. props.setParentState(false) }, [state]) ``` From 11f6ba6dc27bb92de3323e5f44900003bcbd4038 Mon Sep 17 00:00:00 2001 From: Jack Works <5390719+Jack-Works@users.noreply.github.com> Date: Fri, 11 Oct 2024 02:37:01 +0900 Subject: [PATCH 08/10] Update index.ts --- .../index.ts | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/src/use-component-will-receive-update/index.ts b/src/use-component-will-receive-update/index.ts index c8bd3d00..1d9e1524 100644 --- a/src/use-component-will-receive-update/index.ts +++ b/src/use-component-will-receive-update/index.ts @@ -1,42 +1,8 @@ import { useState } from 'react'; /** - * If you're using useEffect like this: - * - * ```js - * const [state, setState] = useState(false) - * useEffect(() => setState(false), [props.someProp]) - * ``` - * Don't do it. See [Adjusting some state when a prop changes](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes) - * It should be like this: - * ```js - * const [prev, setPrev] = useState(state) - * if (prev !== state) { - * setPrev(state) - * setState(false) - * } - * ``` - * This hook is a helper for the above pattern. - * ```js - * useComponentWillReceiveUpdate(() => setState(false), [state]) - * ``` - * - * This only applies to states of the current component. - * Modifying states from other components causes React reporting errors. - * You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) - * and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). - * If you really need to edit other components' states, write it like this: - * - * ```js - * useComponentWillReceiveUpdate(() => { - * setLocalState(false) - * Promise.resolve().then(() => props.setParentState(false)) - * }, [state]) - * ``` - * - * @param callback - * @param deps - */ + * @see {https://foxact.skk.moe/use-component-will-receive-update} + */ export function useComponentWillReceiveUpdate(callback: () => void, deps: readonly unknown[]) { deps = [...deps]; const [prev, setPrev] = useState(deps); From eccd2a1734d5bdf1af6c78578b25e244f9896d89 Mon Sep 17 00:00:00 2001 From: Sukka Date: Fri, 11 Oct 2024 10:34:35 +0800 Subject: [PATCH 09/10] Update docs/src/pages/use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index 774429fc..869b26fa 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -54,6 +54,8 @@ You may also want to read [(Avoid) Notifying parent components about state chang If you need to edit other components' states, write it like this: ```js +import { flushSync } from 'react-dom' + useComponentWillReceiveUpdate(() => { // Force React to immediately flush scheduled/batched updates flushSync(() => setLocalState(false)) From 7c4220f7f686e31a527e49f3a1baf15b588c5dd3 Mon Sep 17 00:00:00 2001 From: Sukka Date: Fri, 11 Oct 2024 10:37:09 +0800 Subject: [PATCH 10/10] Update docs/src/pages/use-component-will-receive-update.mdx --- docs/src/pages/use-component-will-receive-update.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/pages/use-component-will-receive-update.mdx b/docs/src/pages/use-component-will-receive-update.mdx index 869b26fa..e6f770d6 100644 --- a/docs/src/pages/use-component-will-receive-update.mdx +++ b/docs/src/pages/use-component-will-receive-update.mdx @@ -48,10 +48,9 @@ This hook is a helper for the above pattern. useComponentWillReceiveUpdate(() => setState(false), [state]) ``` -This only applies to states of the current component. -Modifying states from other components causes React reporting errors. -You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). -If you need to edit other components' states, write it like this: +This should only apply to states of the current component. Modifying states of other components causes React reporting errors. You may also want to read [(Avoid) Notifying parent components about state changes](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes) and [(Avoid) Passing data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent). + +If you really need to directly modify other components' states (E.g. when working with third-party libraries/components/SDKs where you don't have control of that code), use `flushSync` to separate two state updates: ```js import { flushSync } from 'react-dom'