The errors and warnings you are actually going to hit, in React's own words.
27 entries across 32 errors and warnings. M counts distinct React messages, not error classes.
Every one of these is real. Each snippet declares what React must say, either
an error, like TypeError with a fragment of its message, or the console warnings it
must print. The build runs it against the vendored React and checks: if it does not die or warn
exactly as declared, the build fails and this page is not written. So what you see under each
snippet is what React printed for exactly that code. The directory each snippet ran in is shown
as /app; the names, messages and line numbers are untouched.
Rules of hooks
Hooks are found by the order they are called in. These are what React says when that order changes, or when a hook has no component to belong to.
ErrorA hook that runs only some of the time
A hook that runs only some of the time
dies with Error · in happy-dom 20.11.6 · React dev
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node conditional.jsx
ok the first render was fine
React has detected a change in the order of Hooks called by Toggled. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks
Previous render Next render
------------------------------------------------------
1. useState useState
2. useEffect useEffect
3. undefined useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/app/node_modules/react-dom/cjs/react-dom-client.development.js:7848
throw Error("Rendered more hooks than during the previous render.");
^
Error: Rendered more hooks than during the previous render.
at updateWorkInProgressHook (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7848:17)
at updateReducer (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7967:18)
at Object.useState (/app/node_modules/react-dom/cjs/react-dom-client.development.js:26511:18)
at process.env.NODE_ENV.exports.useState (/app/node_modules/react/cjs/react.development.js:1263:34)
at Toggled (/app/conditional.jsx:8:20)
at Object.react_stack_bottom_frame (/app/node_modules/react-dom/cjs/react-dom-client.development.js:25904:20)
at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7662:22)
at updateFunctionComponent (/app/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19)
at beginWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
Node.js v26.7.0
What it means. React finds a component's state by the order of its hook calls. On the second render a hook appeared that was not there the first time, so every slot after it is off by one. React prints a table of the two orders, then stops.
The fix. Call every hook on every render, at the top of the component, and put the condition inside or after the hook. Lesson 21 breaks the rule each way and shows the fixed twin.
ErrorA hook after an early return
A hook after an early return
dies with Error · in happy-dom 20.11.6 · React dev
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node early-return.jsx
ok the first render was fine
/app/node_modules/react-dom/cjs/react-dom-client.development.js:7717
throw Error(
^
Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
at finishRenderingHooks (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7717:15)
at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7684:7)
at updateFunctionComponent (/app/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19)
at beginWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at performUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22)
at workLoopSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)
at renderRootSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)
at performWorkOnRoot (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16583:35)
at performWorkOnRootViaSchedulerTask (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18957:7)
Node.js v26.7.0
What it means. The second render returned before it reached a hook the first render had called, so there are fewer hooks than React remembers. There is no console warning first: React just stops.
The fix. Move every hook above the first return. A component may return early, but only after it has called all of its hooks.
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node outside.mjs
ok the import itself is fine
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.
/app/node_modules/react/cjs/react.development.js:1263
return resolveDispatcher().useState(initialState);
^
TypeError: Cannot read properties of null (reading 'useState')
at process.env.NODE_ENV.exports.useState (/app/node_modules/react/cjs/react.development.js:1263:33)
at loadName (file:///app/outside.mjs:4:18)
at file:///app/outside.mjs:9:13
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at async node:internal/modules/esm/loader:650:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v26.7.0
What it means. A hook needs a component being rendered to belong to. Called from a plain function there is none, so React warns first, and then the call fails because the hook reaches for a renderer that is not there. That is why the last error is a TypeError about null, not a React message.
The fix. Call the hook from a component or from another hook (a function whose name starts with use). A plain function that needs the value takes it as an argument.
Rendering
What React says when what you returned is not something it can draw: a list without keys, an object where a child belongs, a component that is not one.
ErrorAn object as a child (server, development)
An object as a child (server, development)
dies with Error · server (no DOM) · React dev
object.jsx
1
2
3
4
5import { renderToString } from 'react-dom/server';
const user = { name: 'Ada' };
console.log(renderToString(<p>{user}</p>));
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node object.jsx
/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6474
throw Error(
^
Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
at retryNode (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6474:17)
at renderNodeDestructive (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6367:11)
at renderNode (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6905:18)
at renderElement (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5619:18)
at retryNode (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6428:21)
at performWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7435:17)
at startWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:8270:7)
at renderToStringImpl (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:8380:7)
at process.env.NODE_ENV.exports.renderToString (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9869:14)
at <anonymous> (/app/object.jsx:5:13)
Node.js v26.7.0
What it means. JSX can show text, numbers, elements and arrays of them. A plain object is none of those, and React names the keys it found so you can tell which object it was.
The fix. Show a field of the object, {user.name}, or map it to elements. If you meant a list, pass an array.
warningThe same mistake in the production build
The same mistake in the production build
warns, on purpose · in happy-dom 20.11.6 · React prod
minified.jsx
1
2
3
4
5
6
7
8
9
10import { createRoot } from 'react-dom/client';
const user = { name: 'Ada' };
const root = createRoot(stage);
root.render(<p>{user}</p>);
await new Promise((resolve) => setTimeout(resolve, 50));
check('what the stage holds', stage.innerHTML, '');
root.unmount();
What node printed (React 19.2.7, production build), when the build ran it
$ NODE_ENV=production node minified.jsx
Error: Minified React error #31; visit https://react.dev/errors/31?args[]=object%20with%20keys%20%7Bname%7D for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at throwOnInvalidObjectTypeImpl (/app/node_modules/react-dom/cjs/react-dom-client.production.js:3294:9)
at reconcileChildFibersImpl (/app/node_modules/react-dom/cjs/react-dom-client.production.js:3943:7)
at /app/node_modules/react-dom/cjs/react-dom-client.production.js:3964:29
at reconcileChildren (/app/node_modules/react-dom/cjs/react-dom-client.production.js:6118:9)
at beginWork (/app/node_modules/react-dom/cjs/react-dom-client.production.js:7689:7)
at performUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.production.js:11305:14)
at workLoopSync (/app/node_modules/react-dom/cjs/react-dom-client.production.js:11182:37)
at renderRootSync (/app/node_modules/react-dom/cjs/react-dom-client.production.js:11163:7)
at performWorkOnRoot (/app/node_modules/react-dom/cjs/react-dom-client.production.js:10728:40)
at performWorkOnRootViaSchedulerTask (/app/node_modules/react-dom/cjs/react-dom-client.production.js:12078:3)
ok what the stage holds
What it means. The production build drops the long messages to keep the download small. What is left is a number and a link. The link is shown here as text only: this course does not go online. The number is the key to the full message, and error 31 is the object-as-a-child message above.
The fix. Reproduce the problem in development, where the full text is printed, then fix it the same way.
ErrorA component that is undefined
A component that is undefined
dies with Error · server (no DOM) · React dev
widgets.jsx
1
2
3export function Widget() {
return <p>widget</p>;
}
main.jsx
1
2
3
4
5
6import * as widgets from './widgets.jsx';
import { renderToString } from 'react-dom/server';
const Widgit = widgets.Widgit;
console.log(renderToString(<Widgit />));
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node main.jsx
/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6119
throw Error(
^
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
at renderElement (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6119:15)
at retryNode (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6428:21)
at performWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7435:17)
at startWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:8270:7)
at renderToStringImpl (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:8380:7)
at process.env.NODE_ENV.exports.renderToString (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9869:14)
at <anonymous> (/app/main.jsx:6:13)
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at async node:internal/modules/esm/loader:650:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v26.7.0
What it means. The name in the JSX looks up to undefined, usually because of a misspelled name or a default export imported as a named one (or the reverse). React cannot render undefined and says what it expected.
The fix. Check the import against the export: spelling, braces for a named export, none for a default one. The message also names the file of the component that rendered it.
warningA list without keys
A list without keys
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node App.jsx
Each child in a list should have a unique "key" prop.
Check the render method of `List`. See https://react.dev/link/warning-keys for more information.
ok how many items rendered
What it means. React matches the items of a list from one render to the next by their key. Without one it falls back to position, which is wrong the moment the list is reordered, filtered or inserted into. It still renders: the warning is about what will go wrong later.
The fix. Give each item a key that is stable and unique among its siblings, such as an id from your data: <li key={name}>. Lesson 7 covers keys, and lesson 22 shows what a wrong key does to state.
warningTwo items with the same key
Two items with the same key
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node App.jsx
Encountered two children with the same key, `Ada`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
ok how many items rendered
What it means. A key only works if it is unique among its siblings. With two the same, React cannot tell the items apart, and an update may duplicate or drop one of them.
The fix. Key by something that is unique in your data, such as an id. Do not use a value that can repeat, like a name, and avoid the index if the list can be reordered.
warningA key that arrives through a spread
A key that arrives through a spread
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node App.jsx
A props object containing a "key" prop is being spread into JSX:
let props = {key: someKey, label: ...};
<Label {...props} />
React keys must be passed directly to JSX without using spread:
let props = {label: ...};
<Label key={someKey} {...props} />
ok what the label shows
What it means.key is not a prop: React reads it off the element and keeps it. Spreading an object that has one hides the key from the compiler, which React now warns about because the key will stop being applied correctly in future versions.
The fix. Pass the key by itself and spread the rest: <Label key={props.key} {...rest} />.
ErrorAn input with children
An input with children
dies with Error · in happy-dom 20.11.6 · React dev
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node void.jsx
/app/node_modules/react-dom/cjs/react-dom-client.development.js:20565
throw Error(
^
Error: input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.
at setInitialProperties (/app/node_modules/react-dom/cjs/react-dom-client.development.js:20565:29)
at completeWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:12662:18)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at completeUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17777:19)
at performUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17658:11)
at workLoopSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)
at renderRootSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)
at performWorkOnRoot (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16583:35)
at performWorkOnRootViaSchedulerTask (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18957:7)
at flushActQueue (/app/node_modules/react/cjs/react.development.js:590:34)
Node.js v26.7.0
What it means. Some HTML elements, such as input, img and br, cannot contain anything. React refuses to render one that is given children.
The fix. Close the element yourself, <input />, and put the text in a label next to it, or in the value or placeholder prop.
DOM props
HTML has its own names and nesting rules, and React tells you when JSX does not follow them.
warningA camelCase prop HTML does not have
A camelCase prop HTML does not have
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node App.jsx
React does not recognize the `fooBar` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `foobar` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
ok the element React rendered
What it means. React passes unknown lowercase attributes through, but a camelCase name that is not an HTML property is almost always a mistake, so it warns and still draws the element.
The fix. Use the real name (tabIndex, htmlFor), or write a custom attribute in lowercase, such as data-foo-bar.
warningclass instead of className
class instead of className
warns, on purpose · in happy-dom 20.11.6 · React dev
App.jsx
1
2
3
4
5
6
7import { act } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(stage);
await act(async () => root.render(<div class="box">hi</div>));
check('what the DOM got for class', stage.firstChild.getAttribute('class'), 'box');
await act(async () => root.unmount());
What node printed (React 19.2.7, development build), when the build ran it
$ node App.jsx
Invalid DOM property `class`. Did you mean `className`?
ok what the DOM got for class
What it means. JSX attributes follow the DOM property names, not the HTML attribute names, and the DOM property for class is className. React warns, but still writes the attribute.
The fix. Write className. The same goes for htmlFor instead of for.
warningA div inside a p
A div inside a p
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node App.jsx
In HTML, <div> cannot be a descendant of <p>.
This will cause a hydration error.
> <p>
> <div>
ok the HTML React wrote
What it means. A browser that reads this HTML closes the paragraph before the div, so the markup it builds is not the markup you wrote. React builds the nodes directly and keeps your shape, but server HTML would be rewritten and fail to hydrate, so it warns now.
The fix. Use an element that is allowed inside a paragraph, such as span, or change the p to a div.
Infinite loops
A component that sets state while it renders, or an effect that sets the thing it depends on. React stops each one, and says how.
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node loop.jsx
/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:3973
throw Error(
^
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
at dispatchAction (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:3973:15)
at Counter (/app/loop.jsx:6:3)
at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5072:22)
at renderElement (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5497:23)
at retryNode (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6428:21)
at performWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7435:17)
at startWork (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:8270:7)
at renderToStringImpl (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:8380:7)
at process.env.NODE_ENV.exports.renderToString (/app/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:9869:14)
at <anonymous> (/app/loop.jsx:10:13)
Node.js v26.7.0
What it means. Setting state in the body of a component makes React render it again straight away, which sets state again. After about 25 rounds React stops and throws rather than hang the program.
The fix. Do not call a setter while rendering. Set state in an event handler or an effect, or compute the value during render instead of storing it.
warningsetState during render (client)
setState during render (client)
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node client-loop.jsx
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
at renderWithHooksAgain (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7747:17)
at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-client.development.js:7665:21)
at updateFunctionComponent (/app/node_modules/react-dom/cjs/react-dom-client.development.js:10166:19)
at beginWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:11778:18)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at performUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17641:22)
at workLoopSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)
at renderRootSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)
at performWorkOnRoot (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16583:35)
at performSyncWorkOnRoot (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18972:7)
An error occurred in the <F> component.
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.
ok what the stage holds
What it means. The same loop on the client. The error is reported to the console instead of thrown at your code, together with the name of the component, and nothing is drawn.
The fix. The same as on the server: leave the setter out of the render body. An error boundary (lesson 27) is how an app shows something useful when this happens anyway.
warningA layout effect that sets its own dependency
A layout effect that sets its own dependency
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node layout-loop.jsx
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
at getRootForUpdatedFiber (/app/node_modules/react-dom/cjs/react-dom-client.development.js:4624:11)
at enqueueConcurrentHookUpdate (/app/node_modules/react-dom/cjs/react-dom-client.development.js:4584:14)
at dispatchSetStateInternal (/app/node_modules/react-dom/cjs/react-dom-client.development.js:9167:18)
at dispatchSetState (/app/node_modules/react-dom/cjs/react-dom-client.development.js:9127:7)
at <anonymous> (/app/layout-loop.jsx:10:5)
at Object.react_stack_bottom_frame (/app/node_modules/react-dom/cjs/react-dom-client.development.js:25989:20)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at commitHookEffectListMount (/app/node_modules/react-dom/cjs/react-dom-client.development.js:13249:29)
at commitHookLayoutEffects (/app/node_modules/react-dom/cjs/react-dom-client.development.js:13213:11)
at commitLayoutEffectOnFiber (/app/node_modules/react-dom/cjs/react-dom-client.development.js:13951:13)
An error occurred in the <F> component.
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.
ok React stopped the loop
What it means. The effect sets the very value it depends on, so each run causes the next. For effects that run before the browser paints, React counts the nested updates and, past a limit, stops and reports it.
The fix. Do not set state from an effect that reads the same state unless something stops the chain: add a condition, or derive the value during render. Lesson 14 covers dependencies.
warningAn effect that sets its own dependency
An effect that sets its own dependency
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node effect-loop.jsx
Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
renders > 50: true
ok renders > 50
What it means. An effect with no dependency array runs after every render, and this one sets state, so it causes another render, and so on. React warns each time it sees the pattern. Left alone it would not stop, so the panel stops it at 60 renders (and unmounts after half a second); the page prints only that it went past 50, because the real speed depends on the machine.
The fix. Give the effect a dependency array that does not contain what it sets, or remove the effect and compute the value in render. Lesson 14 and lesson 16 cover both.
warningUpdating another component while rendering
Updating another component while rendering
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node other.jsx
Cannot update a component (`A`) while rendering a different component (`B`). To locate the bad setState() call inside `B`, follow the stack trace as described in https://react.dev/link/setstate-in-render
ok what both components show
What it means. Rendering is meant to be a pure calculation. Here B, while it renders, tells A to change, which React cannot allow: A may already have been drawn this pass.
The fix. Move the call out of render, into an event handler or an effect, or lift the state up to the parent that renders both (lesson 12).
Forms
Controlled and uncontrolled inputs, and the warnings for mixing them up.
warningAn input that changes from uncontrolled to controlled
An input that changes from uncontrolled to controlled
warns, on purpose · in happy-dom 20.11.6 · React dev
switch.jsx
1
2
3
4
5
6
7
8
9
10
11
12import { act } from 'react';
import { createRoot } from 'react-dom/client';
function Field({ value }) {
return <input value={value} onChange={() => {}} />;
}
const root = createRoot(stage);
await act(async () => root.render(<Field value={undefined} />));
await act(async () => root.render(<Field value="Ada" />));
check('the value after the switch', stage.querySelector('input').value, 'Ada');
await act(async () => root.unmount());
What node printed (React 19.2.7, development build), when the build ran it
$ node switch.jsx
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components
ok the value after the switch
What it means. An input with no value is controlled by the browser; one with a value is controlled by React. This one started as the first and became the second, usually because the state it reads started as undefined.
The fix. Start the state as a string, useState(''), so the input is controlled from the first render. Lesson 11 covers controlled inputs.
warningA value with no onChange
A value with no onChange
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node readonly.jsx
You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
ok the value after typing
What it means. React owns the value, and nothing tells it what to do when someone types. So it puts the old value back, and the field cannot be edited.
The fix. Add an onChange that updates state, readOnly if it should not change, or defaultValue if the browser should keep the value.
Effects and async
Effects that return the wrong thing, updates that React was not told to expect, and components that cannot be async on the client.
TypeErrorAn async function as an effect
An async function as an effect
dies with TypeError · in happy-dom 20.11.6 · React dev
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node async-effect.jsx
useEffect must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching
ok the first render was fine
/app/node_modules/react-dom/cjs/react-dom-client.development.js:26001
destroy();
^
TypeError: destroy is not a function
at Object.react_stack_bottom_frame (/app/node_modules/react-dom/cjs/react-dom-client.development.js:26001:13)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at commitHookEffectListUnmount (/app/node_modules/react-dom/cjs/react-dom-client.development.js:13316:17)
at commitHookPassiveUnmountEffects (/app/node_modules/react-dom/cjs/react-dom-client.development.js:13347:11)
at commitPassiveUnmountEffectsInsideOfDeletedTree_begin (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16264:13)
at recursivelyTraversePassiveUnmountEffects (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16085:13)
at commitPassiveUnmountOnFiber (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16127:11)
at flushPassiveEffects (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18428:9)
at flushPendingEffects (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18358:14)
at flushSpawnedWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18323:44)
Node.js v26.7.0
What it means. An async function always returns a promise, and React reads what an effect returns as its clean-up function. It warns when the effect runs, and when the component unmounts it tries to call the promise as a function.
The fix. Keep the effect itself synchronous and start the async work inside it: define an async function in the effect and call it. Lesson 13 shows the pattern.
warningflushSync inside an effect
flushSync inside an effect
warns, on purpose · in happy-dom 20.11.6 · React dev
flush.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { useEffect, act } from 'react';
import { flushSync } from 'react-dom';
import { createRoot } from 'react-dom/client';
let ran = false;
function F() {
useEffect(() => {
flushSync(() => {});
ran = true;
}, []);
return <p>hi</p>;
}
const root = createRoot(stage);
await act(async () => root.render(<F />));
check('whether the effect ran', ran, true);
await act(async () => root.unmount());
What node printed (React 19.2.7, development build), when the build ran it
$ node flush.jsx
flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.
ok whether the effect ran
What it means.flushSync forces React to update the page right now, which it cannot do while it is already in the middle of an update. The call is ignored for that moment, and React says so.
The fix. Take the call out of the effect. If it must run after a render, schedule it with a setTimeout or a microtask.
warningAn update outside act in a test
An update outside act in a test
warns, on purpose · in happy-dom 20.11.6 · React dev
no-act.jsx
1
2
3
4
5
6
7
8
9
10
11import { act } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(stage);
await act(async () => root.render(<p>one</p>));
root.render(<p>two</p>);
await new Promise((resolve) => setTimeout(resolve, 50));
check('the text after the update', stage.textContent, 'two');
await act(async () => root.unmount());
What node printed (React 19.2.7, development build), when the build ran it
$ node no-act.jsx
An update to Root inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
ok the text after the update
What it means. In a test, React is told it is running under act, which collects every update so the test can wait for them. An update made outside it may not be finished when the test looks.
The fix. Wrap the update in await act(async () => ...). Lesson 34 shows it in a test file.
warningAn async component on the client
An async component on the client
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node async-component.jsx
An update to Root inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
<A> is an async Client Component. Only Server Components can be async at the moment. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.
A suspended resource finished loading inside a test, but the event was not wrapped in act(...).
When testing, code that resolves suspended data should be wrapped into act(...):
act(() => {
/* finish loading suspended data */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.
ok it rendered anyway
An update to Root inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
A suspended resource finished loading inside a test, but the event was not wrapped in act(...).
When testing, code that resolves suspended data should be wrapped into act(...):
act(() => {
/* finish loading suspended data */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
A suspended resource finished loading inside a test, but the event was not wrapped in act(...).
When testing, code that resolves suspended data should be wrapped into act(...):
act(() => {
/* finish loading suspended data */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.
What it means. A component that is an async function returns a promise. Only a server component may do that. On the client React warns about the component and about the promise it suspended on, although the small one in the first half of the panel still draws. The second half renders outside act, as in the missing-act entry, which adds the warning about a resource that finished loading unnoticed.
The fix. Load the data in an effect, or read a promise with use (lesson 26), or fetch on the server and pass the result down.
Server rendering
What React says when the HTML from the server and the first render in the browser disagree.
warningHydration text mismatch
Hydration text mismatch
warns, on purpose · in happy-dom 20.11.6 · React dev
What node printed (React 19.2.7, development build), when the build ran it
$ node mismatch.jsx
Error: Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
<h1>
+ hi client
- hi server
at throwOnHydrationMismatch (/app/node_modules/react-dom/cjs/react-dom-client.development.js:5238:11)
at prepareToHydrateHostInstance (/app/node_modules/react-dom/cjs/react-dom-client.development.js:5334:21)
at completeWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:12553:15)
at runWithFiberInDEV (/app/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at completeUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17777:19)
at performUnitOfWork (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17658:11)
at workLoopSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17469:41)
at renderRootSync (/app/node_modules/react-dom/cjs/react-dom-client.development.js:17450:11)
at performWorkOnRoot (/app/node_modules/react-dom/cjs/react-dom-client.development.js:16504:11)
at performWorkOnRootViaSchedulerTask (/app/node_modules/react-dom/cjs/react-dom-client.development.js:18957:7)
ok the client text won
What it means. The HTML from the server and the first render in the browser must match. They did not, so React cannot reuse the HTML, reports the difference and renders the client's version over it. It is a recoverable error, not a crash.
The fix. Render the same thing on both sides. Values that change between the two, such as dates, random numbers and typeof window, belong in an effect. Lesson 32 covers hydration.
Server/client boundary
Code that belongs on one side of the server/client line, imported on the other.
SyntaxErrorA client-only hook in a server component
A client-only hook in a server component
dies with SyntaxError · server (no DOM) · React dev
1
2
3
4import { Counter } from './Counter.jsx';
import { renderToString } from 'react-dom/server';
console.log(renderToString(<Counter />));
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node --conditions=react-server server.jsx
/app/Counter.jsx:1
import { useState } from 'react';
^
SyntaxError: The requested module 'react' does not provide an export named 'useState'
at #asyncInstantiate (node:internal/modules/esm/module_job:463:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:561:5)
at async node:internal/modules/esm/loader:650:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v26.7.0
What it means. A server component gets a build of React with the client hooks removed (--conditions=react-server). A component that imports useState asks for something that is not there. This is the module system speaking, not React. No RSC payload is produced here; the Next.js course covers the framework side.
The fix. Move the stateful part into its own file that begins with 'use client' and render it from the server component. Lesson 33 shows the split.
Errorserver-only code imported from client code
server-only code imported from client code
dies with Error · server (no DOM) · React dev
secret.js
1
2
3import 'server-only';
export const secret = 'only on the server';
client.mjs
1
2
3import { secret } from './secret.js';
console.log(secret);
What node printed as it died (React 19.2.7, development build), when the build ran it
$ node client.mjs
/app/node_modules/server-only/index.js:1
throw new Error(
^
Error: This module cannot be imported from a Client Component module. It should only be used from a Server Component.
at Object.<anonymous> (/app/node_modules/server-only/index.js:1:7)
at Module._compile (node:internal/modules/cjs/loader:1934:14)
at Object..js (node:internal/modules/cjs/loader:2074:10)
at Module.load (node:internal/modules/cjs/loader:1656:32)
at Module._load (node:internal/modules/cjs/loader:1448:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
at loadCJSModuleWithModuleLoad (node:internal/modules/esm/translators:373:15)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:245:9)
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at async node:internal/modules/esm/loader:650:26
Node.js v26.7.0
What it means. The server-only package is empty under the react-server condition and throws under every other. Importing it from code that is not on the server fails at once, before the secret can end up in a bundle the browser downloads.
The fix. Keep the file out of the client code: import it only from server components, and pass the client a plain value it needs.
How to read any of them. In development React writes a sentence that says
what is wrong, usually names the component, and often says what to do. A warning is printed to
the console and the program carries on; an error stops the render. The text of a production
error is a code and a link, and the link is shown as text only: this course does not go online.
Newsletter
New writing, when it lands.
Occasional notes on shipping software with AI agents. No schedule, no filler, unsubscribe in one click.