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?