Several times in the past few years I’ve encountered this sort of problem, but just left the inefficient code in place because couldn’t find a clear way to solve it. So I’m finally asking how to solve it nicely.
Here is some basic code to isolate the core problem. I have a useState(complexDefaultComputation)
below, and I’d like to know how to not compute that after each render, which also not having to resort to useEffect
to set the initial value.
import React, { useState } from 'react';
import _ from 'lodash';
type MyRecord = {
id: string;
// 20 more props...
};
export default function MyComponent({ records }: { records: Array<MyRecord> }) {
const [lastPageLoadedIndex, setLastPageLoadedIndex] = useState(0);
const [pages, setPages] = useState(
_.chunk(records, 10).map((chunk, i) => ({
records: chunk,
loaded: i === 0,
}))
);
const handleClick = () => {
const page = pages[lastPageLoadedIndex + 1];
// async fetch pages...
// then update state
pages[lastPageLoadedIndex + 1] = { ...page, loaded: true };
setLastPageLoadedIndex((i) => i + 1);
setPages([...pages]);
};
return (
<div>
{pages
.map((page, pageIndex) =>
page.loaded
? page.records.map((record, recordIndex) => (
<div key={`record-${pageIndex}-${recordIndex}`}>
{record.id}
</div>
))
: []
)
.flat()}
<button onClick={handleClick}>Load next</button>
</div>
);
}
How can I do that?
I tried:
useEffect
to set the initial value, but then it has to render at least twice, and we don’t get SEO benefits.useMemo
to set the default, followed by auseState
with the memoized value? Is that the desired approach?
2
Answers
You can pass a function into
useState
with initialization code. The function will only be called on the first render, and it’s return value will become the initial state.https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state
you can: useCallback
this caches the function between renders
https://react.dev/reference/react/useCallback
useMemo on the other hand stores the result of a function
https://react.dev/reference/react/useMemo
you can also write the initial value as a function that returns the variable, and this will only evaluate on the first paint.
Theres a lot of articles about detailling when to use which one.
but here is the true answer: unless you have an immediate performance issue don’t bother. The problems around caching are being solved in react 19 along with a heap of new hooks so old hooks that deal with caching will be deprecated in the next major release.
Installing React 18.3 will give to the warning you need to prepare for the update. Which looks pretty minor if you’re already past 17.0.2