facebook/react · error · Error
536
536
Error message
Cannot pass ref in renderToHTML because they will never be hydrated.
What it means
react-markup's experimental_renderToHTML produces plain HTML that is never hydrated, so a ref could never be attached; the markup Fizz host config (ReactFizzConfigMarkup) hard-throws at pushStart when any host element carries a non-null ref prop. The throw happens during rendering and rejects the renderToHTML promise via the destination's destroy path.
Source
Thrown at packages/react-markup/src/ReactFizzConfigMarkup.js:124
return parentContext;
}
export function pushStartInstance(
target: Array<Chunk | PrecomputedChunk>,
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,View on GitHub (pinned to eafeac097b)
Solutions
- Remove ref from every element passed to renderToHTML.
- In shared components, make the ref conditional (skip it when rendering static markup, e.g. behind an isStatic prop or a separate code path).
- If you need element access, insert the returned HTML string client-side and use querySelector instead of a ref.
Example fix
// before
await experimental_renderToHTML(<input ref={inputRef} />);
// after
const html = await experimental_renderToHTML(<input />);
// after inserting the HTML:
container.insertAdjacentHTML('beforeend', html);
container.querySelector('input').focus(); Defensive patterns
Strategy: validation
Validate before calling
import {cloneElement, isValidElement} from 'react';
function stripRefs(node) {
if (Array.isArray(node)) return node.map(stripRefs);
if (!isValidElement(node)) return node;
const {ref, ...props} = node.props; // eslint-disable-line no-unused-vars
if (props.children != null) props.children = stripRefs(props.children);
return cloneElement(node, {...props, ref: null});
}
const html = await experimental_renderToHTML(stripRefs(<App />)); Prevention
- Keep refs out of any tree passed to renderToHTML - the output is never hydrated.
- Make refs optional in shared components used by both client and static-markup pipelines.
- Query the DOM after insertion instead of using refs on static HTML.
When it happens
Trigger: Passing ref to any host element inside the tree given to experimental_renderToHTML - including refs added inside shared design-system components, inputs, or HOCs that unconditionally forward refs.
Common situations: Reusing client components or pages in a static-markup pipeline; component libraries whose inputs/buttons always attach refs; migrating from ReactDOMServer.renderToStaticMarkup to renderToHTML without removing refs.
Related errors
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/00f04f8ffba70fbf.
Report an issue: GitHub.