facebook/react · error · Error
299
299
Error message
Target container is not a DOM element.
What it means
createPortal validates its container with isValidContainer, which accepts only DOM nodes whose nodeType is ELEMENT (1), DOCUMENT (9), or DOCUMENT_FRAGMENT (11) (plus a special mount-point comment). Passing null/undefined — classically a failed document.getElementById — or a non-DOM value such as a selector string or a jQuery-wrapped object throws error 299 before any portal is created.
Source
Thrown at packages/react-dom/src/client/ReactDOMClientFB.js:82
// $FlowFixMe[prop-missing] Flow incorrectly thinks Set has no prototype
Set.prototype == null ||
typeof Set.prototype.clear !== 'function' ||
typeof Set.prototype.forEach !== 'function'
) {
console.error(
'React depends on Map and Set built-in types. Make sure that you load a ' +
'polyfill in older browsers. https://react.dev/link/react-polyfills',
);
}
}
function createPortal(
children: ReactNodeList,
container: Element | DocumentFragment,
key: ?string = null,
): React$Portal {
if (!isValidContainer(container)) {
throw new Error('Target container is not a DOM element.');
}
// TODO: pass ReactDOM portal implementation as third argument
// $FlowFixMe[incompatible-type] The Flow type is opaque but there's no way to actually create it.
return createPortalImpl(children, container, null, key);
}
// Overload the definition to the two valid signatures.
// Warning, this opts-out of checking the function body.
declare function flushSyncFromReconciler<R>(fn: () => R): R;
declare function flushSyncFromReconciler(): void;
function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
if (__DEV__) {
if (isAlreadyRendering()) {
console.error(
'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.',View on GitHub (pinned to eafeac097b)
Solutions
- Null-check the getElementById result before rendering the portal
- Ensure the container <div id="..."> exists in the DOM before the code runs (script at end of body, or defer)
- Fix mismatched or typo'd container ids between HTML and JavaScript
- Render the portal only after the container is mounted (e.g. inside useEffect)
Example fix
// before
createPortal(<Tooltip />, document.getElementById('tooltip-root'));
// after
const host = document.getElementById('tooltip-root');
if (host) {
createPortal(<Tooltip />, host);
} Defensive patterns
Strategy: type-guard
Validate before calling
function isValidPortalContainer(node: unknown): boolean {
return !!(
node &&
typeof node === 'object' &&
(node.nodeType === 1 /* ELEMENT */ ||
node.nodeType === 9 /* DOCUMENT */ ||
node.nodeType === 11 /* DOCUMENT_FRAGMENT */)
);
}
const host = document.getElementById('tooltip-root');
if (isValidPortalContainer(host)) {
createPortal(<Tooltip />, host);
} Type guard
function isValidPortalContainer(node: unknown): node is Element | Document | DocumentFragment {
return !!(
node &&
typeof node === 'object' &&
(node.nodeType === 1 || node.nodeType === 9 || node.nodeType === 11)
);
} Try / catch
try {
createPortal(children, host);
} catch (e) {
if (e instanceof Error && e.message === 'Target container is not a DOM element.') {
throw new Error(`Portal host missing: ${host}`); // clearer context for debugging
}
throw e;
} Prevention
- Always null-check getElementById results before using them as portal containers
- Create portals inside useEffect or lifecycle code that runs after the host element exists
- Keep container ids in a shared constant so HTML and JS cannot drift apart
When it happens
Trigger: createPortal(<Modal />, document.getElementById('modal-root')) when no element with that id exists; passing a CSS selector string, a jQuery/$() result, or a plain object instead of a DOM node.
Common situations: The portal container element is rendered later than the script runs; the id in the JSX/HTML does not match the id in JS; the container div was never added to the page template.
Related errors
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/36a226068f2d017a.
Report an issue: GitHub.