preactjs/preact · critical · Error
Invalid type passed to createElement(): ${type} Did you acc
Error message
Invalid type passed to createElement(): ${type}
Did you accidentally pass a JSX literal as JSX twice?
let My${getDisplayName(vnode)} = ${serializeVNode(type)};
let vnode = <My${getDisplayName(vnode)} />;
This usually happens when you export a JSX literal and not the component.
${getOwnerStack(vnode)} What it means
Thrown from options._diff when vnode.type is a non-null object that looks like an already-created vnode (it has both _children and _dom properties). This signals a JSX literal was used where a component was expected — i.e. an element was passed into createElement as the type rather than as children. The diagnostic message even sketches the mistaken pattern (`let MyX = <X/>; let vnode = <MyX/>`).
Source
Thrown at debug/src/debug.js:156
if (oldRoot) oldRoot(vnode, parentNode);
};
options._diff = vnode => {
let { type } = vnode;
hooksAllowed = true;
if (type === undefined) {
throw new Error(
'Undefined component passed to createElement()\n\n' +
'You likely forgot to export your component or might have mixed up default and named imports' +
serializeVNode(vnode) +
`\n\n${getOwnerStack(vnode)}`
);
} else if (type != null && typeof type == 'object') {
if (type._children !== undefined && type._dom !== undefined) {
throw new Error(
`Invalid type passed to createElement(): ${type}\n\n` +
'Did you accidentally pass a JSX literal as JSX twice?\n\n' +
` let My${getDisplayName(vnode)} = ${serializeVNode(type)};\n` +
` let vnode = <My${getDisplayName(vnode)} />;\n\n` +
'This usually happens when you export a JSX literal and not the component.' +
`\n\n${getOwnerStack(vnode)}`
);
}
throw new Error(
'Invalid type passed to createElement(): ' +
(Array.isArray(type) ? 'array' : type)
);
}
if (
vnode.ref !== undefined &&
typeof vnode.ref != 'function' &&View on GitHub (pinned to e881e7e838)
Solutions
- Find the export that is an element and convert it to a component: `export function Header() { return <h1/>; }` instead of `export const Header = <h1/>`.
- If you need a pre-configured element, wrap it: `export const Header = () => <h1 className='x'/>`.
- Trace the import chain back to the definition and confirm `typeof Definition === 'function'`.
- Search the codebase for `= <` assignments in exported constants — these are the likely culprits.
Example fix
// before
export const Card = <div className='card' />;
// consumer
import { Card } from './Card';
render(<Card />, root);
// after
export function Card() {
return <div className='card' />;
}
render(<Card />, root); Defensive patterns
Strategy: type-guard
Validate before calling
function assertComponentNotElement(component, name) {
if (component && typeof component === 'object' && '_children' in component && '_dom' in component) {
throw new Error(`"${name}" is a JSX element, not a component. Wrap it: function C(){ return element; }`);
}
if (typeof component !== 'function') {
throw new Error(`"${name}" is not a renderable component (typeof ${typeof component}).`);
}
return component;
}
import { Card } from './Card';
assertComponentNotElement(Card, 'Card'); Type guard
/** @param {unknown} c */
function isVNodeLike(c) {
return c != null && typeof c === 'object' && '_children' in c && '_dom' in c;
}
/** @param {unknown} c */
function isComponent(c) {
return typeof c === 'function' || typeof c === 'string';
} Prevention
- Never export a JSX literal — always export a component function or class.
- Search the codebase for `export const X = <` patterns during review.
- When wrapping a pre-configured element, return it from a function component.
When it happens
Trigger: Exporting a JSX element instead of a component: `export const Header = <h1/>` then using `<Header/>`; storing the result of `h()` or a JSX expression and then rendering it as a component type; double-wrapping JSX: `const Wrapper = <Comp/>; <Wrapper/>`; spreading an element into createElement's first arg.
Common situations: Refactor that turned a component into a configured element for convenience (e.g. `export const Button = <Button variant='primary'/>`); HOC that mistakenly returns an element rather than a component; copying a pattern from a config file where a default element was exported; passing preact-render-to-string output back into render.
Related errors
- Undefined component passed to createElement() You likely fo
- Invalid type passed to createElement(): ${Array.isArray(type
- Objects are not valid as a child. Encountered an object with
- Undefined parent passed to render(), this is the second argu
- Expected a valid HTML node as a second argument to render. R
AI-assisted analysis of preactjs/preact@e881e7e838 (2026-08-13).
Data as JSON: /api/errors/9f6c1b7f13657b02.
Report an issue: GitHub.