Skip to content

Commit

Permalink
[Hooks] Add recommendations about useMemo and lazy init (#1565)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Jan 16, 2019
1 parent 17fdc42 commit d7d5533
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 1 deletion.
67 changes: 66 additions & 1 deletion content/docs/hooks-faq.md
Expand Up @@ -41,6 +41,7 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo
* [Can I skip an effect on updates?](#can-i-skip-an-effect-on-updates)
* [How do I implement shouldComponentUpdate?](#how-do-i-implement-shouldcomponentupdate)
* [How to memoize calculations?](#how-to-memoize-calculations)
* [How to create expensive objects lazily?](#how-to-create-expensive-objects-lazily)
* [Are Hooks slow because of creating functions in render?](#are-hooks-slow-because-of-creating-functions-in-render)
* [How to avoid passing callbacks down?](#how-to-avoid-passing-callbacks-down)
* [How to read an often-changing value from useCallback?](#how-to-read-an-often-changing-value-from-usecallback)
Expand Down Expand Up @@ -342,7 +343,9 @@ const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

This code calls `computeExpensiveValue(a, b)`. But if the inputs `[a, b]` haven't changed since the last value, `useMemo` skips calling it a second time and simply reuses the last value it returned.

Conveniently, this also lets you skip an expensive re-render of a child:
`useMemo` is treated as a hint rather than guarantee. React may still choose to "forget" some previously memoized values to free memory, and recalculate them on next render.

Conveniently, `useMemo` also lets you skip an expensive re-render of a child:

```js
function Parent({ a, b }) {
Expand All @@ -361,6 +364,67 @@ function Parent({ a, b }) {

Note that this approach won't work in a loop because Hook calls [can't](/docs/hooks-rules.html) be placed inside loops. But you can extract a separate component for the list item, and call `useMemo` there.

### How to create expensive objects lazily?

`useMemo` lets you [memoize an expensive calculation](#how-to-memoize-calculations) if the inputs are the same. However, it only serves as a hint, and doesn't *guarantee* the computation won't re-run. But sometimes need to be sure an object is only created once.

**The first common use case is when creating the initial state is expensive:**

```js
function Table(props) {
// ⚠️ createRows() is called on every render
const [rows, setRows] = useState(createRows(props.count));
// ...
}
```

To avoid re-creating the ignored initial state, we can pass a **function** to `useState`:

```js
function Table(props) {
// ✅ createRows() is only called once
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
```

React will only call this function during the first render. See the [`useState` API reference](/docs/hooks-reference.html#usestate).

**You might also occasionally want to avoid re-creating the `useRef()` initial value.** For example, maybe you want to ensure some imperative class instance only gets created once:

```js
function Image(props) {
// ⚠️ IntersectionObserver is created on every render
const ref = useRef(new IntersectionObserver(onIntersect));
// ...
}
```

`useRef` **does not** accept a special function overload like `useState`. Instead, you can write your own function that creates and sets it lazily:

```js
function Image(props) {
const ref = useRef(null);

// ✅ IntersectionObserver is created lazily once
function getObserver() {
let observer = ref.current;
if (observer !== null) {
return observer;
}
let newObserver = new IntersectionObserver(onIntersect);
ref.current = newObserver;
return newObserver;
}

// When you need it, call getObserver()
// ...
}
```

This avoids creating an expensive object until it's truly needed for the first time. If you use Flow or TypeScript, you can also give `getObserver()` a non-nullable type for convenience.


### Are Hooks slow because of creating functions in render?

No. In modern browsers, the raw performance of closures compared to classes doesn't differ significantly except in extreme scenarios.
Expand Down Expand Up @@ -497,6 +561,7 @@ function useEventCallback(fn, dependencies) {

In either case, we **don't recommend this pattern** and only show it here for completeness. Instead, it is preferable to [avoid passing callbacks deep down](#how-to-avoid-passing-callbacks-down).


## Under the Hood

### How does React associate Hook calls with components?
Expand Down
2 changes: 2 additions & 0 deletions content/docs/hooks-reference.md
Expand Up @@ -290,6 +290,8 @@ Pass a "create" function and an array of inputs. `useMemo` will only recompute t

If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument. (With an inline function, on every render.)

**Don't rely on `useMemo` for correctness.** React treats it as an optimization hint and does not *guarantee* to retain the memoized value. For example, React may choose to "forget" some previously memoized values to free memory, and recalculate them on next render.

> Note
>
> The array of inputs is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically.
Expand Down

0 comments on commit d7d5533

Please sign in to comment.