preactjs/preact · critical · Error

Invalid type passed to createElement(): ${Array.isArray(type

Error message

Invalid type passed to createElement(): ${Array.isArray(type) ? 'array' : type}

What it means

Thrown from options._diff when vnode.type is a non-null object that does NOT carry the _children/_dom markers (so it is not a stray vnode), but is still invalid as a component type — e.g. a plain object, a Map, a Promise, or an array. Arrays are reported literally as 'array' for clarity. This is the catch-all for object types Preact cannot render.

Source

Thrown at debug/src/debug.js:166

			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' &&
			typeof vnode.ref != 'object' &&
			!('$$typeof' in vnode) // allow string refs when preact-compat is installed
		) {
			throw new Error(
				`Component's "ref" property should be a function, or an object created ` +
					`by createRef(), but got [${typeof vnode.ref}] instead\n` +
					serializeVNode(vnode) +
					`\n\n${getOwnerStack(vnode)}`
			);
		}

View on GitHub (pinned to e881e7e838)

Solutions

  1. Inspect vnode.type at runtime: `console.log(typeof type, type)` — it must be a function, class, or string.
  2. If the component is defined as an object literal, wrap it in a function: `const Comp = () => <obj {...props}/>`.
  3. For arrays, map children instead: `<>{items.map(i => <Item key={i.id} />)}</>` rather than `<items/>`.
  4. Check that imports resolve to a function/class — re-export issues can yield object module namespace records.

Example fix

// before
const config = { label: 'Hi' };
render(<config />, root);

// after
function Config() {
  return <div>{config.label}</div>;
}
render(<Config />, root);
Defensive patterns

Strategy: type-guard

Validate before calling

function assertValidVNodeType(type) {
  if (type == null || typeof type === 'object') {
    throw new Error(`Invalid vnode type: ${Array.isArray(type) ? 'array' : type}. Expected string|function|class.`);
  }
  return type;
}

// before constructing a vnode dynamically
assertValidVNodeType(MyType);

Type guard

/** @param {unknown} t */
function isValidPreactType(t) {
  if (typeof t === 'string' || typeof t === 'function') return true;
  // Allow null to skip rendering, disallow other objects
  return t === null;
}

Prevention

When it happens

Trigger: Passing a plain config object as a component: `<{} as any/>`; passing an array of components where one component was expected; passing a Promise (pre-Suspense) or a Map/Set as type; spreading a props object into the type position by mistake.

Common situations: Accidental destructuring swap: `const { type } = config; <type/>` where type is an object; HOC pipeline that returns a descriptor object instead of a component; migrating from a framework whose components are objects (e.g. Vue options objects) without wrapping; type-erased TypeScript where a discriminated union collapses to object.

Related errors


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