Skip to content

Cannot update a component while rendering a different component

Cannot update a component while rendering a different component: the name React blames is the wrong one. Find the real setState call and move it out of render.

React react-hooks reactjs debugging state-management
BKG
Reading Progress

On This Page

Three. That's how many times the parent rendered in my smallest repro: a child that returns null, a parent whose state ended up exactly where I wanted it, and a page that looked right. Two I could have explained. The third came with one line in the console, and it's the line you probably searched for.

What the console prints in 18 and in 19

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

React 18.3.1 prints the same sentence with a Warning: prefix and tacks a component stack on the end of the arguments:

Warning: Cannot update a component (`ParentA`) while rendering a different component (`Child`). To locate the bad setState() call inside `Child`, follow the stack trace as described in https://react.dev/link/setstate-in-render
    at Child (/src/Child.jsx:2:19)
    at ParentA (/src/ParentA.jsx:3:5)

In 19.0.7 the prefix is gone but the stack argument is still there. On 19.2.7 and 19.3.1 the console.error call carries only the format string and three component names; any stack you see in the browser is added by the console or by your framework's overlay. That's what I measured by capturing the raw arguments, not what the docs say.

There is no minified code for this one. It's a development-only warning. Production builds stay silent and do the same thing anyway (more on that below).

Reproduce it with a Vitest file

I used a test instead of a dev server because it lets me capture console.error and count calls. Pinned versions, so you get what I got:

mkdir repro && cd repro
npm init -y
npm pkg set type=module
npm i react@19.3.1 react-dom@19.3.1 vitest@5.0.5 jsdom@30.1.3 \
  @testing-library/react@16.3.3 @testing-library/dom@10.4.2 @vitejs/plugin-react@6.1.2
// vitest.config.mjs
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', globals: true } })
// repro.test.jsx
import React, { useState } from 'react'
import { render } from '@testing-library/react'
globalThis.IS_REACT_ACT_ENVIRONMENT = true

function ParentA() {
  const [v, setV] = useState(0)
  return <><Child onValue={setV} /><p>{v}</p></>
}
function Child({ onValue }) {
  onValue(1) // <- a write to ParentA's state, executed while Child renders
  return null
}

test('warns once', () => {
  const calls = []
  const orig = console.error
  console.error = (...a) => calls.push(a)
  render(<ParentA />)
  console.error = orig
  expect(calls).toHaveLength(1)
})
npx vitest run repro.test.jsx

Expected: one passing test, and the warning above if you print calls[0]. If the test fails with 0 calls, you're probably on a production build of React (NODE_ENV=production), which never emits it. If you get a different sentence ("Cannot update during an existing state transition"), you rendered a class component; hold that thought for the version table.

Cleanup is rm -rf repro.

Same sentence on every version I ran, packaged differently

React Result in the repro Notes
18.3.1 Warns once Prefix Warning: , component stack appended to the arguments
19.0.7 Warns once No prefix, stack still appended
19.2.7 Warns once No prefix, no stack argument
19.3.1 Warns once Same as 19.2.7
Any, production build Silent Same renders, same final DOM

Two more rows that matter. For class components, the same code path logs a different message: "Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state." That one has its own boolean and fires once per page load. And in React 16.13 this warning first showed up in the wild; the original complaint thread, which you can reach through the react.dev/link/setstate-in-render redirect, is a person upgrading a big app and drowning in it. It's been a warning ever since. Nothing in 19 made it an error.

If your problem is a render that loops until React gives up, that's a different failure: see Too many re-renders: what React is limiting and its sibling Too many re-renders, causes and fixes. This warning can exist without any loop. That's why it's easy to ignore.

Two names in one sentence, and the wrong one gets blamed

Read the sentence again. The first name, in backticks, is the component being updated. The second is the component that was rendering when the update happened. In my repro that's ParentA and Child.

The natural move is to look at the first name. Reasonable: it's first, it's the one whose state is wrong. But ParentA did nothing wrong. It's a victim. The guilty code is inside Child, or inside something Child calls during its render, and it's holding a setter it was handed from above.

Plain version: some component's function body called another component's state setter. React renders in a pass where function bodies are supposed to compute JSX from props and state, nothing else. A write to somebody else's state in that pass is a side effect in the wrong phase.

Why React can't just let the write through

Here is the tell I wish I'd known: in the shipped development build, the warning isn't in the hook code. It's in scheduleUpdateOnFiber, the function every state update eventually calls. I found it in node_modules/react-dom/cjs/react-dom-client.development.js (19.3.1), and the condition is narrower than the message suggests:

  1. React is inside the render phase (an internal flag says so).
  2. The root being updated is the root currently rendering.
  3. A "currently rendering" flag is true, meaning we're inside a component function, not, say, a commit or a layout effect.
  4. The fiber being updated is a function component, a forwardRef, or a simple memo component. (Classes take the other branch.)

When all of those line up, React looks at the work-in-progress fiber, which is the component rendering right now, gets its name, and logs. It uses that name as the dedupe key. Remember that; it bites later.

Why is it a problem? React renders top-down. By the time Child runs, ParentA has already finished its function body and read its state. A write now can't be reflected in what ParentA already returned. So React has to mark the update for a later pass. A plain createRoot run in jsdom, no StrictMode, counted three calls to the parent function, and the DOM ended up v=1, which is correct. I didn't trace why it's three and not two, so I won't guess. What I can say is that the same count came out of the development and the production build, so the extra work isn't a dev-only artifact.

The same-component version is different and legal: if a component calls its own setter during its own render, React throws away that output and immediately re-runs the function (the pattern the useState reference calls "storing information from previous renders"). That branch never reaches this warning. In my test H, a component with a guarded setA(a + 1) of its own state, logged zero warnings.

React's own docs also say updater functions must be pure, and calling another component's setter during rendering is an error in their words. The warning is the enforcement. Production builds don't include the warning (the production run logged zero calls with the same count of three renders), so the code that nags in dev carries on silently for your users.

Finding the call when the stack won't help

The stack points at your component's function. It doesn't point at the line where the setter was invoked unless the call is in the body. Usually it isn't. It's in a helper, an updater function, a hook, a store.

The wrong turns

Wrong turn one: StrictMode. The component rendered more times than I expected, and StrictMode renders everything twice in development, so that was my first theory. I rendered the repro inside <StrictMode> and counted: Child ran four times, the warning fired once. Then I took StrictMode off and the warning was still there. StrictMode multiplies renders; it doesn't create this warning. Dropped.

Wrong turn two: a guard. If Child only calls the setter when needed, the loop can't happen, right? I changed it to:

function Child2({ v, onValue }) {
  if (v !== 1) onValue(1) // looks safe
  return null
}

Same warning, same sentence, Child2 named as the renderer. The guard stops repeated writes. It doesn't change the fact that the first one happens in render. It's the first thing most people try, and it silences nothing.

A diagnostic path that works in someone else's app

# 1. Which name is the renderer? Take the SECOND backticked name.
#    That's where to start reading, not the first.

# 2. Is the setter a prop? Search for who passes it down.
grep -rn "onValue=" src | head

# 3. Open the renderer in the debugger. Add a breakpoint on the line
#    that calls the prop / setter. Or, faster, wrap the setter in the parent:
#    const setVLogged = (x) => { console.trace('who called setV?'); setV(x) }

Step 3 is the one that cracks it. console.trace prints the JavaScript stack at the call, and that includes the helper or hook that actually pulled the trigger, which the React warning's own stack drops.

Then, to classify, ask what's around the call:

Is the call in the component's body, directly?        -> cause 1
Is it inside a useMemo/useState initializer callback?  -> cause 2
Is it inside a function passed to a setState (updater)?-> cause 3
Is it a store/context write that other components read?-> cause 4
Is it behind a callback prop invoked during render?    -> cause 1, one level removed

The ranked causes

Rank Cause How common (my ordering from constructed cases, no telemetry) The tell Fix
1 A child calls a parent's setter or callback prop in its body Most common Second name in the warning is the caller; the call is onSomething(x) next to return Option A or B
2 Side effect inside useMemo or useState(() => ...) Common Warning names the component with the hook; the callback touches a prop setter Option A
3 A setter called inside an updater function (setX(x => { setY(...); return x })) Less common Warning appears on click, not on mount; renderer is the component owning x Option C
4 An external store written during render (store.set() in a body) Rare, hurts most Updated name is the subscriber (Reader), renderer is the writer Option A or D
5 Duplicated names hide a second offender Rare, nasty Fix one and a new warning appears much later See below

Which one did I hit first? Number one, one level removed. I wrote a form field that called props.onValidityChange(isValid) in its body so a parent could disable a Save button, because the validity computation lived in the child. That placement is what makes it feel natural to write.

What I verified for causes 2 to 4

  • Cause 2: useMemo(() => { setX(5) }, []) inside Memo produced the same sentence, with Memo as the renderer.
  • Cause 3: for this to warn, the updater has to write to another component's state. A test where setC(x => { setP(99); return x + 1 }) lived in a child that owned c and received setP from its parent warned (PB updated, CB rendering). A first version of my test, where both setters belonged to one component, did not warn at all, because that's a same-component update. My original test was wrong; this is why you should test your diagnosis.
  • Cause 4: a useSyncExternalStore subscriber (Reader) plus a Writer that calls store.set(5) when a prop changes produced "Cannot update a component (Reader) while rendering a different component (Writer)". My first test missed because the subscription is created after mount, so the write has to happen on a later render.

The dedupe trap

Run two tests in a row:

function PF() { /* two different children, each calling a parent setter */ }
// first render: two warnings, one for X1 and one for X2
// later render of PG that uses X1 again: zero warnings

I measured two warnings for two different renderers and zero for a repeat of one already seen. The dedupe key is the renderer's name, not the pair. So after Child has warned once, a new bug in Child with a different parent stays quiet for the rest of the page load. If you fix the first and the warning vanishes, you still might not be done. Test after the fix with a fresh page.

Four rewrites, ranked

Option A: move the write out of render into an event handler. If a user action causes the update, put it where the action happens. This is the shape I'd use for the Save button case:

-function Field({ value, onValidityChange }) {
-  onValidityChange(value.length > 2)
-  return <input defaultValue={value} />
-}
+function Field({ value, onChange }) {
+  return <input value={value} onChange={(e) => onChange(e.target.value)} />
+}

The parent derives isValid from value itself. No child-to-parent write exists anymore.

Option B: derive the parent's data during render. If the parent can compute it from state it already owns, delete the state:

-const [isValid, setIsValid] = useState(false)
-<Field value={value} onValidityChange={setIsValid} />
+const isValid = value.length > 2
+<Field value={value} onChange={setValue} />

This is the fix I'd ship almost every time.

Option C: keep updaters pure. An updater function returns the next state and does nothing else:

-setC(x => { setP(99); return x + 1 })
+setC(x => x + 1)
+setP(99)

React's docs say updaters must be pure, and StrictMode calls them twice in development to catch you.

Option D: if you really need to push into the parent after a child's value settles, do it in an effect.

function C1({ value, onValue }) {
  useEffect(() => { onValue(value) }, [value, onValue])
  return null
}

I tested this: zero warnings, parent ended up with the value. I don't love it. It adds a render after paint for something that should have been derived, which is why Option B comes first.

Silencers to avoid: wrapping the call in setTimeout(() => onValue(x)) or queueMicrotask hides the warning because it's no longer in the render pass, but it hands you a flash of wrong content and a race when the component unmounts. I didn't profile this beyond reasoning, so call it a warning from experience, not a measurement. The if (v !== 1) guard doesn't help, as above. And filtering the message out of your logger is how you end up with three renders in production.

Who owns this state?

The bug class goes away when you can answer one question for every piece of state: which component owns it? A child shouldn't hold a setter for a parent's state as its job. It should receive a value and an onChange, and call onChange from events.

A version I'd ship, with every import:

import { useState } from 'react'

function Field({ value, onChange }: { value: string; onChange: (v: string) => void }) {
  return <input value={value} onChange={(e) => onChange(e.target.value)} />
}

export function Form() {
  const [value, setValue] = useState('')
  const isValid = value.length > 2 // derived, not stored
  return (
    <form>
      <Field value={value} onChange={setValue} />
      <button disabled={!isValid}>Save</button>
    </form>
  )
}

State lives in Form. Field is a pure function of props. The next person can't write the bug because there's nothing to call.

For stores, write in handlers or effects and never in a component body. Same rule, different object.

Lint won't see it: what will

I ran eslint-plugin-react-hooks 7.1.1 against three components. It ships a rule called react-hooks/set-state-in-render in the recommended-latest flat config. It caught this:

export function Own() { const [a, setA] = useState(0); setA(a + 1); return a }

with "Cannot call setState during render". It said nothing about Child, because onValue is just a function prop to the linter. It can't know the function is a setter from somewhere else. So the main cause, the one in my table at number one, is invisible to the rule. That was the surprise of the whole investigation, and it's why I trust a review habit over a lint rule here.

What I'd put in place:

  • Keep react-hooks/set-state-in-render and react-hooks/set-state-in-effect enabled. They catch the same-component cases.
  • A review habit: any prop named onSomething called outside a handler or effect is a bug until proven otherwise.
  • A test helper that fails on console.error containing "while rendering a different component". Vitest or Jest setupFiles: spy on console.error, fail the test. Dev-only warnings are exactly the ones production won't tell you about.
  • In CI, run Playwright or Cypress with console errors as failures on one smoke path.
  • For production: this warning isn't emitted, so watch the symptom instead. If INP or render counts rise on a screen that shouldn't have an update loop, open the React Profiler and look for a parent re-rendering because of a child.

On the next upgrade, confirm on a dev build that the sentence still reads the same. Across the four versions I ran, it changed in prefix and stack arguments only, never in meaning.

Pin these

  • The first name is the victim; read the second.
  • A guard around the setter stops the loop, not the warning.
  • Dev warns, production burns renders silently.
  • Lint can't see a setter hiding in a prop.
  • The dedupe key is the renderer's name, so retest after fixing.

Quick answers people search for

Why does React say "Cannot update a component while rendering a different component"? Some function component called another component's state setter while React was rendering. It's a dev-only warning from React 16.13 onward, and it's in 18 and 19.

Is the warning safe to ignore? The UI usually ends up correct, but you pay extra renders, and a loop can follow once the condition changes. I measured three parent renders for one trivial case.

How do I find which setState caused it? Take the second component name in the message, find the callback prop it calls, and wrap the setter with console.trace in the parent. See Hydration mismatch, causes and fixes for another case where the named component isn't the cause, and Invalid hook call when hooks are fine for a similar "wrong suspect" trap.

Does StrictMode cause it? No. StrictMode doubles renders in development. I got four renders of the child and still one warning, and the warning stays with StrictMode removed.

Reactreact-hooksreactjsdebuggingstate-management

BKG

Engineer and writer behind CODELZ. I read the stack trace, reproduce the bug, and explain why it happens, not just how to silence it. Plus honest reviews of the books and tools worth your time.

Comments