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

  1. Find the export that is an element and convert it to a component: `export function Header() { return <h1/>; }` instead of `export const Header = <h1/>`.
  2. If you need a pre-configured element, wrap it: `export const Header = () => <h1 className='x'/>`.
  3. Trace the import chain back to the definition and confirm `typeof Definition === 'function'`.
  4. 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

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


AI-assisted analysis of preactjs/preact@e881e7e838 (2026-08-13). Data as JSON: /api/errors/9f6c1b7f13657b02. Report an issue: GitHub.