testing-library/react-testing-library · error · Error
Attempted to hydrate a non-hydrateable root. This is a bug i
Error message
Attempted to hydrate a non-hydrateable root. This is a bug in `@testing-library/react`.
What it means
In createConcurrentRoot (src/pure.js), the returned root object exposes a hydrate() method that is only meaningful when the root was created via hydrateRoot. If the internal `hydrate` flag is false the root was built with createRoot, so calling hydrate() throws. The message says 'This is a bug in `@testing-library/react`' because the library itself should never call hydrate on a non-hydrateable root; users cannot normally trigger it through the public API.
Source
Thrown at src/pure.js:136
strictModeIfNeeded(
wrapUiIfNeeded(ui, WrapperComponent),
reactStrictMode,
),
{onCaughtError, onRecoverableError},
)
})
} else {
root = ReactDOMClient.createRoot(container, {
onCaughtError,
onRecoverableError,
})
}
return {
hydrate() {
/* istanbul ignore if */
if (!hydrate) {
throw new Error(
'Attempted to hydrate a non-hydrateable root. This is a bug in `@testing-library/react`.',
)
}
// Nothing to do since hydration happens when creating the root object.
},
render(element) {
root.render(element)
},
unmount() {
root.unmount()
},
}
}
function createLegacyRoot(container) {
return {
hydrate(element) {
ReactDOM.hydrate(element, container)View on GitHub (pinned to 20ce75f290)
Solutions
- Pass {hydrate: true} to render() if you intend to hydrate server-rendered markup, so the root is created with hydrateRoot.
- Do not call (or trigger) hydrate() on a root created without the hydrate option; use render() instead.
- If it appears without custom code, report it as a bug to @testing-library/react with a reproduction.
- Verify you are not mixing roots created by different render() calls (hydrated vs non-hydrated) in a custom wrapper.
Example fix
// before
render(<App />, { container })
root.hydrate()
// after
render(<App />, { container, hydrate: true }) Defensive patterns
Strategy: validation
Validate before calling
// Only trigger hydration when you opted in
const options = isServerMarkup ? { hydrate: true } : {}
render(<App />, options) Type guard
function canHydrate(renderOptions) {
return typeof renderOptions === 'object' && renderOptions !== null && renderOptions.hydrate === true
} Try / catch
try {
render(<App />, { hydrate: true })
} catch (e) {
if (String(e.message).includes('non-hydrateable root')) {
// retry as a plain client render
render(<App />)
} else {
throw e
}
} Prevention
- Only set hydrate: true when the container actually contains server-rendered HTML.
- Never call internal root.hydrate() methods yourself; use the public render() API.
- Keep @testing-library/react updated; this error is flagged as an internal bug by the library itself.
- Do not reuse root objects across hydrated and non-hydrated render calls.
When it happens
Trigger: renderRoot calls root.hydrate() on a root object produced by createConcurrentRoot with hydrate: false (i.e. render() was called without {hydrate: true}), a state inconsistency inside the library.
Common situations: Practically only seen when a custom wrapper or patched internals of @testing-library/react mix hydrated and non-hydrated roots, or when passing an externally created root object into renderRoot with mismatched options.
Related errors
AI-assisted analysis of testing-library/react-testing-library@20ce75f290 (2026-09-02).
Data as JSON: /api/errors/a8d5c05367e2336d.
Report an issue: GitHub.