Ch. 3 · React

React: Cannot Update a Component While Rendering a Different One

Fix the React warning 'Cannot update a component while rendering a different component': a child sets parent state during render.

~8 min readintermediateupdated Oct 4, 2026

The page seems to work, but the console shows a red warning:

Cannot update a component (`ProductSearch`) while rendering a different component (`ResultList`). To locate the bad setState() call inside `ResultList`, follow the stack trace as described in https://react.dev/link/setstate-in-render
Text

React 18 prints the same text with a Warning: prefix and a reactjs.org link. It is a development-only warning, not a crash: while React was running ResultList (the second name), something inside it called a state setter owned by ProductSearch (the first name). Rendering is supposed to be a pure calculation, and this is a side effect on a component that has already rendered.

Quick fix checklist

  • Open the component named second; it made the call. The one named first owns the state.
  • Search its body for calls to props such as onChange(...), onCount(...), onLoad(...) or setters from context, outside of handlers and effects.
  • If the parent only needs a value the child computes, compute it in the parent and pass the result down.
  • If the update is a reaction to a user action, call it inside that event handler.
  • If it is a notification after render (for example, a measured size), move it into useEffect with correct dependencies.
  • If the updated component is a class, the warning reads Cannot update during an existing state transition (such as within `render`); the fixes are the same.

Before you start

You should be comfortable with lifting state up (a parent owning state and passing a setter to a child) and with the difference between rendering, event handlers and effects. The behaviour here is the same in React 18 and 19.

Why it happens

React renders top-down. By the time it runs ResultList, it has already called ProductSearch and holds its JSX. A child calling a parent’s setter now means the parent’s output is stale before it reaches the screen, so React must render the parent again (and therefore the child again) right after. Here is the smallest version:

function ProductSearch() {
  const [count, setCount] = useState(0);
  return (
    <>
      <p>{count} results</p>
      <ResultList query="ap" onCount={setCount} />
    </>
  );
}

function ResultList({ query, onCount }) {
  const matches = products.filter((p) => p.name.includes(query));
  onCount(matches.length); // updating the parent during the child's render
  return <ul>{matches.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}
JSX

It “works”: React re-renders and the paragraph eventually shows the right number. That is exactly why the warning matters. The code depends on React rendering things twice and on render running exactly once per commit, neither of which React promises:

  • Concurrent rendering can start a render and throw it away (for example, when a transition is interrupted by a more urgent update). The parent’s state has already been changed by a render that never reached the screen.
  • StrictMode calls components twice in development to catch exactly this kind of impurity, so the setter runs twice.
  • Loops. If the value is new every time (an object, an array, a timestamp), each parent render sets new state, which renders the child, which sets state again. In development you see this warning, then React reports Maximum update depth exceeded while the page keeps re-rendering.

Compare this with a component updating its own state during render: React supports that narrow case (it re-runs the same component immediately before going further), which is why it produces a different error, Too many re-renders, only when it repeats endlessly.

Common real-world triggers include a child calling onChange, onValidChange or onCount during render, form libraries’ setValue called in a component body, a context provider’s setter (setTitle in a layout context) called from a page component, and calling router navigation functions while rendering.

Step-by-step walkthrough

Step 1: Decode the names

The message has the pattern Cannot update a component (`A`) while rendering a different component (`B`). A owns the state; B is the culprit. React logs the warning only once per culprit component per page load, so after editing code, reload the page before concluding it is gone.

Step 2: Follow the stack trace

Expand the console warning. In React 19 the stack shows the call path inside B, ending at the setter call. Look for the first frame in your own source: it is usually a prop callback (onCount), a function from context, or a custom hook that wraps one. If a custom hook is involved, the call may be one level deeper than the component file.

Step 3: Ask why the parent needs the update

The right fix depends on what the update represents:

Situation Fix
The parent needs a value derived from data it already has Compute it in the parent, pass results down
The value changes because the user did something Call the callback in the event handler
The parent needs information only known after render (DOM size, a loaded resource) useEffect or useLayoutEffect in the child
Child-local state should reset when something changes Use a key on the child instead of syncing

Step 4: Lift the derivation

For the first row, move the calculation to the component that needs it. The child becomes a pure display component:

function ProductSearch() {
  const [query, setQuery] = useState('');
  const matches = products.filter((p) => p.name.includes(query));

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <p>{matches.length} results</p>
      <ResultList matches={matches} />
    </>
  );
}

function ResultList({ matches }) {
  return <ul>{matches.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}
JSX

There is no count state at all: a value you can compute during render should not be state. See derived state for more on this rule.

Step 5: Use handlers or effects for the rest

When the update follows a user action, do it in the handler, where both updates are batched into one render:

function QuantityInput({ value, onChange, onValidChange }) {
  function handleChange(e) {
    const next = Number(e.target.value);
    onChange(next);
    onValidChange(Number.isInteger(next) && next > 0);
  }
  return <input type="number" value={value} onChange={handleChange} />;
}
JSX

When the parent truly needs something only available after render, notify it from an effect:

useEffect(() => {
  onHeightChange(ref.current.offsetHeight);
}, [onHeightChange]);
JSX

This costs an extra render and needs a stable callback (wrap it in useCallback in the parent, or the effect runs every render). That trade-off is why the effect is the last option, not the first.

Worked scenario

A dashboard layout shows a page title in its header. Pages set the title through context:

const TitleContext = createContext(() => {});

function DashboardLayout({ children }) {
  const [title, setTitle] = useState('Dashboard');
  return (
    <TitleContext.Provider value={setTitle}>
      <header><h1>{title}</h1></header>
      <main>{children}</main>
    </TitleContext.Provider>
  );
}

function InvoicesPage({ invoices }) {
  const setTitle = useContext(TitleContext);
  setTitle(`Invoices (${invoices.length})`);
  return <InvoiceTable invoices={invoices} />;
}
JSX

The console shows Cannot update a component (`DashboardLayout`) while rendering a different component (`InvoicesPage`). Diagnosis: InvoicesPage calls the layout’s setter during its own render. The title is a side effect on another component, and it should be applied after InvoicesPage actually commits, which is what effects are for. (Passing the title as a prop from the route configuration would be even better if your router supports it.)

function InvoicesPage({ invoices }) {
  const setTitle = useContext(TitleContext);
  const title = `Invoices (${invoices.length})`;

  useEffect(() => {
    setTitle(title);
  }, [setTitle, title]);

  return <InvoiceTable invoices={invoices} />;
}
JSX

setTitle is a state setter, so its identity is stable and the effect only re-runs when the title text changes. The warning disappears, and a render that React throws away can no longer change the header.

Common mistake

The most common wrong fix is wrapping the call in setTimeout or queueMicrotask inside the render body: setTimeout(() => onCount(matches.length)). The warning disappears because the setter no longer runs during render, but it still runs once per render attempt, including discarded and StrictMode renders, it happens after the screen has painted the old value, and it creates a new timer on every render. It hides the impurity instead of removing it.

Another is moving every such call into an effect without asking whether the state needs to exist. An effect that copies a child’s computed value into parent state keeps two sources of truth in sync and renders twice per change. If the parent can compute the value, lift the calculation.

Verify the behavior

Fail tests on React warnings so this cannot creep back in. With Vitest or Jest and React Testing Library:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('search renders without cross-component updates', async () => {
  const errorSpy = vi.spyOn(console, 'error'); // jest.spyOn in Jest
  render(<ProductSearch />); // products: 'apple', 'apricot', 'banana'
  await userEvent.type(screen.getByRole('textbox'), 'ap');
  expect(screen.getByText('2 results')).toBeInTheDocument();
  expect(errorSpy).not.toHaveBeenCalled();
  errorSpy.mockRestore();
});
JSX

With the broken version, the spy records the warning on the very first render, so the test fails. In the browser, wrap the app in <StrictMode> and reload: no warning should appear, and the React DevTools Profiler should show a single commit per keystroke.

Interview exercise

Why can a component call its own state setter during render (with a guard), but not a parent’s setter? Both schedule a re-render.

Answer and reasoning

When a component updates its own state during render, React can handle it locally: it discards the JSX that component just returned and calls the same component again with the new state, before rendering children or committing anything. Nothing else has observed the stale value. A parent, however, has already finished rendering and its JSX (including the props it passed down) is part of the work in progress. Updating it from a child means that work is already wrong, so React has to schedule another pass over the parent and its subtree, and the update was triggered by a render that might be thrown away in concurrent rendering or doubled by StrictMode. React therefore treats it as an impure side effect and warns. The fix is to keep data flowing down: derive the value in the owner, or update the owner from an event handler or effect, which run only for renders that actually commit.

Continue learning

Practise with the React interview questions and the React MCQs. Derived state in React explains when a value should be computed rather than stored, callback dependencies covers keeping the callback in an effect stable, and StrictMode double invocation explains why impure renders show up in development. Related errors: Too many re-renders for a component updating itself and Maximum update depth exceeded for loops. On react.dev, read Keeping Components Pure and You Might Not Need an Effect.

More in React

esc