facebook/react · error · Error
537
537
Error message
Cannot pass event handlers (${propKey}) in renderToHTML because the HTML will never be hydrated so they can never get called. What it means
experimental_renderToHTML emits inert HTML that will never be hydrated, so event handler props could never be invoked; the markup Fizz config throws whenever any host-element prop is a function, naming the offending propKey in the message. This is a deliberate guard that static output stays non-interactive.
Source
Thrown at packages/react-markup/src/ReactFizzConfigMarkup.js:129
type: string,
props: Object,
resumableState: ResumableState,
renderState: RenderState,
preambleState: null | PreambleState,
hoistableState: null | HoistableState,
formatContext: FormatContext,
textEmbedded: boolean,
): ReactNodeList {
for (const propKey in props) {
if (hasOwnProperty.call(props, propKey)) {
const propValue = props[propKey];
if (propKey === 'ref' && propValue != null) {
throw new Error(
'Cannot pass ref in renderToHTML because they will never be hydrated.',
);
}
if (typeof propValue === 'function') {
throw new Error(
'Cannot pass event handlers (' +
propKey +
') in renderToHTML because ' +
'the HTML will never be hydrated so they can never get called.',
);
}
}
}
return pushStartInstanceImpl(
target,
type,
props,
resumableState,
renderState,
preambleState,
hoistableState,
formatContext,View on GitHub (pinned to eafeac097b)
Solutions
- Remove function props from all elements rendered by renderToHTML.
- Branch in shared components on a static/isStatic flag to omit handlers during static rendering.
- Attach interactivity client-side: emit data-* attributes and call addEventListener after the HTML string is inserted into the document.
Example fix
// before
const html = await experimental_renderToHTML(
<button onClick={save}>Save</button>,
);
// after
const html = await experimental_renderToHTML(
<button data-action='save'>Save</button>,
);
// after insertion:
container
.querySelector('[data-action=save]')
.addEventListener('click', save); Defensive patterns
Strategy: validation
Validate before calling
import {cloneElement, isValidElement} from 'react';
function forStaticMarkup(node) {
if (Array.isArray(node)) return node.map(forStaticMarkup);
if (!isValidElement(node)) return node;
const next = {};
for (const key in node.props) {
const value = node.props[key];
if (key === 'ref' || typeof value === 'function') continue;
next[key] =
key === 'children' && value != null ? forStaticMarkup(value) : value;
}
return cloneElement(node, next);
}
const html = await experimental_renderToHTML(forStaticMarkup(<App />)); Prevention
- Never pass function props to host elements in a renderToHTML tree.
- Add an isStatic branch to shared components to omit handlers during static rendering.
- Use data-* attributes plus client-side addEventListener for interactivity on static HTML.
When it happens
Trigger: Passing a function prop (onClick, onChange, onSubmit, onPress, ...) to any host element inside the renderToHTML tree - including handlers deep inside reused client components or spread props objects.
Common situations: Dropping a client page/component into a static markup render; components with built-in onSubmit/onClick; spreading untyped props that carry handlers; migrating hydrated SSR code to renderToHTML.
Related errors
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/c78ad26f53602c2e.
Report an issue: GitHub.