Skip to content

Commit d7d5533

Browse files
authored
[Hooks] Add recommendations about useMemo and lazy init (#1565)
1 parent 17fdc42 commit d7d5533

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

content/docs/hooks-faq.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo
4141
* [Can I skip an effect on updates?](#can-i-skip-an-effect-on-updates)
4242
* [How do I implement shouldComponentUpdate?](#how-do-i-implement-shouldcomponentupdate)
4343
* [How to memoize calculations?](#how-to-memoize-calculations)
44+
* [How to create expensive objects lazily?](#how-to-create-expensive-objects-lazily)
4445
* [Are Hooks slow because of creating functions in render?](#are-hooks-slow-because-of-creating-functions-in-render)
4546
* [How to avoid passing callbacks down?](#how-to-avoid-passing-callbacks-down)
4647
* [How to read an often-changing value from useCallback?](#how-to-read-an-often-changing-value-from-usecallback)
@@ -342,7 +343,9 @@ const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
342343

343344
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.
344345

345-
Conveniently, this also lets you skip an expensive re-render of a child:
346+
`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.
347+
348+
Conveniently, `useMemo` also lets you skip an expensive re-render of a child:
346349

347350
```js
348351
function Parent({ a, b }) {
@@ -361,6 +364,67 @@ function Parent({ a, b }) {
361364

362365
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.
363366

367+
### How to create expensive objects lazily?
368+
369+
`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.
370+
371+
**The first common use case is when creating the initial state is expensive:**
372+
373+
```js
374+
function Table(props) {
375+
// ⚠️ createRows() is called on every render
376+
const [rows, setRows] = useState(createRows(props.count));
377+
// ...
378+
}
379+
```
380+
381+
To avoid re-creating the ignored initial state, we can pass a **function** to `useState`:
382+
383+
```js
384+
function Table(props) {
385+
// ✅ createRows() is only called once
386+
const [rows, setRows] = useState(() => createRows(props.count));
387+
// ...
388+
}
389+
```
390+
391+
React will only call this function during the first render. See the [`useState` API reference](/docs/hooks-reference.html#usestate).
392+
393+
**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:
394+
395+
```js
396+
function Image(props) {
397+
// ⚠️ IntersectionObserver is created on every render
398+
const ref = useRef(new IntersectionObserver(onIntersect));
399+
// ...
400+
}
401+
```
402+
403+
`useRef` **does not** accept a special function overload like `useState`. Instead, you can write your own function that creates and sets it lazily:
404+
405+
```js
406+
function Image(props) {
407+
const ref = useRef(null);
408+
409+
// ✅ IntersectionObserver is created lazily once
410+
function getObserver() {
411+
let observer = ref.current;
412+
if (observer !== null) {
413+
return observer;
414+
}
415+
let newObserver = new IntersectionObserver(onIntersect);
416+
ref.current = newObserver;
417+
return newObserver;
418+
}
419+
420+
// When you need it, call getObserver()
421+
// ...
422+
}
423+
```
424+
425+
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.
426+
427+
364428
### Are Hooks slow because of creating functions in render?
365429

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

498562
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).
499563

564+
500565
## Under the Hood
501566

502567
### How does React associate Hook calls with components?

content/docs/hooks-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ Pass a "create" function and an array of inputs. `useMemo` will only recompute t
290290

291291
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.)
292292

293+
**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.
294+
293295
> Note
294296
>
295297
> 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.

0 commit comments

Comments
 (0)