Skip to content

Update docs for stable Hooks #1593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions content/docs/hooks-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo
* [Can I run an effect only on updates?](#can-i-run-an-effect-only-on-updates)
* [How to get the previous props or state?](#how-to-get-the-previous-props-or-state)
* [How do I implement getDerivedStateFromProps?](#how-do-i-implement-getderivedstatefromprops)
* [Is there something like forceUpdate?](#is-there-something-like-forceupdate)
* [Can I make a ref to a function component?](#can-i-make-a-ref-to-a-function-component)
* [What does const [thing, setThing] = useState() mean?](#what-does-const-thing-setthing--usestate-mean)
* **[Performance Optimizations](#performance-optimizations)**
Expand Down Expand Up @@ -322,6 +323,22 @@ function ScrollView({row}) {

This might look strange at first, but an update during rendering is exactly what `getDerivedStateFromProps` has always been like conceptually.

### Is there something like forceUpdate?

Both `useState` and `useReducer` Hooks [bail out of updates](/docs/hooks-reference.html#bailing-out-of-a-state-update) if the next value is the same as the previous one. Mutating state in place and calling `setState` will not cause a re-render.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"bail out" feels jargony – how do you feel about "skip rerendering"?


Normally, you shouldn't mutate local state in React. However, as an escape hatch, you can use an incrementing counter to force a re-render even if the state has not changed:

```js
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

function handleClick() {
forceUpdate();
}
```

Try to avoid this pattern if possible.

### Can I make a ref to a function component?

While you shouldn't need this often, you may expose some imperative methods to a parent component with the [`useImperativeHandle`](/docs/hooks-reference.html#useimperativehandle) Hook.
Expand Down
67 changes: 47 additions & 20 deletions content/docs/hooks-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ const [state, setState] = useState(() => {
});
```

#### Bailing out of a state update

If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the [`Object.is` comparison algorithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description).)

### `useEffect`

```js
Expand Down Expand Up @@ -172,20 +176,18 @@ The following Hooks are either variants of the basic ones from the previous sect
### `useReducer`

```js
const [state, dispatch] = useReducer(reducer, initialState);
const [state, dispatch] = useReducer(reducer, initialArg, init);
```

An alternative to [`useState`](#usestate). Accepts a reducer of type `(state, action) => newState`, and returns the current state paired with a `dispatch` method. (If you're familiar with Redux, you already know how this works.)

`useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. `useReducer` also lets you optimize performance for components that trigger deep updates because [you can pass `dispatch` down instead of callbacks](/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down).

Here's the counter example from the [`useState`](#usestate) section, rewritten to use a reducer:

```js
const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
case 'reset':
return initialState;
case 'increment':
return {count: state.count + 1};
case 'decrement':
Expand All @@ -202,31 +204,59 @@ function Counter({initialCount}) {
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'reset'})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
```

#### Specifying the initial state

There’s a few different ways to initialize `useReducer` state. You may choose either one depending on the use case. The simplest way to pass the initial state as a second argument:

```js{3}
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);
```

>Note
>
>React doesn’t use the `state = initialState` argument convention popularized by Redux. The initial value sometimes needs to depend on props and so is specified from the Hook call instead. If you feel strongly about this, you can write `state = initialState` both in the reducer and inside the `useReducer` destructuring assignment, but it's not encouraged.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I found this very hard to parse due to my unfamiliarity. Maybe…

To specify initial state, React doesn't use the default arguments pattern function reducer(action, state = {count: 0}) popularized by Redux.


you can write state = initialState both in the reducer and inside the useReducer destructuring assignment

can you not put it only in the reducer and let the initial state default to undefined? why?


#### Lazy initialization

`useReducer` accepts an optional third argument, `initialAction`. If provided, the initial action is applied during the initial render. This is useful for computing an initial state that includes values passed via props:
If calculating the initial state is expensive, you can initialize it lazily. In that case, you can skip the second argument (and pass `undefined`). The third `useReducer` argument is an optional `init` function that you can provide to calculate the initial value once:

```js
const initialState = {count: 0};
```js{3-4}
const [state, dispatch] = useReducer(
reducer,
undefined,
() => ({count: initialCount})
);
```

#### Lazy initialization with a transform

For the most flexibility, you can specify *both* the second `initialArg` and the third `init` function arguments. In that case, the initial state will be set to `init(initialArg)`.

This is handy if you want to extract the lazy initialization logic outside your reducer:

```js{1-3,11-12,21,26}
function init(initialCount) {
return {count: initialCount};
}

function reducer(state, action) {
switch (action.type) {
case 'reset':
return {count: action.payload};
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
// A reducer must always return a valid state.
// Alternatively you can throw an error if an invalid action is dispatched.
Expand All @@ -235,12 +265,7 @@ function reducer(state, action) {
}

function Counter({initialCount}) {
const [state, dispatch] = useReducer(
reducer,
initialState,
{type: 'reset', payload: initialCount},
);

const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
Expand All @@ -255,7 +280,9 @@ function Counter({initialCount}) {
}
```

`useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because [you can pass `dispatch` down instead of callbacks](/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down).
#### Bailing out of a dispatch

If you return the same value from a Reducer Hook as the current state, React will bail out without rendering the children or firing effects. (React uses the [`Object.is` comparison algorithm](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description).)

### `useCallback`

Expand Down