FrontendPro

65Debug an infinite useEffect() loop and prevent stale asynchronous responses

mediumAbout 35 minutesCodingreacthooksdebuggingrace-conditions

Problem

A teammate wrote this profile component. In production it hammers the API with requests, the CPU fan spins up, and when users switch profiles quickly they sometimes see the wrong person.

function UserProfile({ userId, loadUser }) {
  const [user, setUser] = useState(null);
  const [requests, setRequests] = useState(0);
  const params = { id: userId, include: ["posts"] };

  useEffect(() => {
    setRequests(requests + 1);
    loadUser(params).then((data) => setUser(data));
  }, [params, requests]);

  return (
    <section data-requests={requests}>
      <h2>{user ? user.name : "Loading…"}</h2>
    </section>
  );
}

loadUser(params) returns a promise that resolves to { id, name }. It also receives a second argument, { signal }, if you want to cancel requests.

Fix UserProfile so that:

  • it requests each userId exactly once and data-requests counts those requests;
  • after userId changes, it shows "Loading…" until the new user arrives;
  • it never shows a user whose id differs from the current userId, whatever order the responses arrive in;
  • no state is updated after it unmounts.

Keep the same props, markup and data-requests attribute.

Try it yourself

Write your solution, run it, then submit to check it against the tests.

App.jsxReact + DOM

Step-by-step

params is a new object on every render, and React compares dependencies with Object.is. A new object is never equal to the old one, so the effect runs after every render.

Unlock the full solution

What's inside

  • Step-by-step
  • Approach
  • Solution
  • Complexity
  • Edge cases
  • Follow-up questions
  • Tests

Includes 2 code listings, 4 edge cases, 5 follow-up questions.

One-time payment by UPI, card or net banking. Launch price.

All 150 answersOne payment, yours forever