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'