Skip to content

Commit

Permalink
feat: useComponentWillReceiveUpdate (#24)
Browse files Browse the repository at this point in the history
Co-authored-by: Sukka <isukkaw@gmail.com>
  • Loading branch information
Jack-Works and SukkaW authored Oct 11, 2024
1 parent 13e8fdd commit ce72368
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
66 changes: 66 additions & 0 deletions docs/src/pages/use-component-will-receive-update.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: useComponentWillReceiveUpdate
---

# useComponentWillReceiveUpdate

import ExportMetaInfo from '../components/export-meta-info';

<ExportMetaInfo />

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

```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) 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)
}
```

This hook is a helper for the above pattern.

```js
useComponentWillReceiveUpdate(() => setState(false), [state])
```

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'

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])
```
18 changes: 18 additions & 0 deletions src/use-component-will-receive-update/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from 'react';

/**
* @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);
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();
}
}

0 comments on commit ce72368

Please sign in to comment.