skip to Main Content

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 a useState with the memoized value? Is that the desired approach?

2

Answers


  1. 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.

    const [pages, setPages] = useState(
      () => _.chunk(records, 10).map((chunk, i) => ({
        records: chunk,
        loaded: i === 0,
      }))
    );
    

    https://react.dev/reference/react/useState#avoiding-recreating-the-initial-state

    Login or Signup to reply.
  2. you can: useCallback
    this caches the function between renders

     const handleSubmit = useCallback((arg) => {
        // do the thing and return a value
      }, [a, b]);
    

    https://react.dev/reference/react/useCallback

    useMemo on the other hand stores the result of a function

     useMemo(
        () => // do the thing and return a value
     [a, b]);
    

    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.

     useState(
        () => // do the thing and return a value
      )
    

    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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search