preactjs/preact · error · Error

Objects are not valid as a child. Encountered an object with

Error message

Objects are not valid as a child. Encountered an object with the keys {${keys}}.

${getOwnerStack(vnode)}

What it means

Thrown from options.diffed after a vnode's children are resolved: if any rendered child is a plain object (typeof 'object', truthy) with no `type` property, Preact rejects it. Valid children are primitives (string/number), arrays, booleans/null/undefined (ignored), or vnodes (which carry a `type`). A bare object literal like `{foo:1}` as a child is not renderable. The check runs in diffed (not vnode) so component children that intentionally accept object shapes are not falsely flagged — only actually-rendered plain objects are caught.

Source

Thrown at debug/src/debug.js:339

		// eslint-disable-next-line
		vnode.__proto__ = deprecatedProto;
		if (oldVnode) oldVnode(vnode);
	};

	options.diffed = vnode => {
		const { type, _parent: parent } = vnode;
		// Check if the user passed plain objects as children. Note that we cannot
		// move this check into `options.vnode` because components can receive
		// children in any shape they want (e.g.
		// `<MyJSONFormatter>{{ foo: 123, bar: "abc" }}</MyJSONFormatter>`).
		// Putting this check in `options.diffed` ensures that
		// `vnode._children` is set and that we only validate the children
		// that were actually rendered.
		if (vnode._children) {
			vnode._children.forEach(child => {
				if (typeof child === 'object' && child && child.type === undefined) {
					const keys = Object.keys(child).join(',');
					throw new Error(
						`Objects are not valid as a child. Encountered an object with the keys {${keys}}.` +
							`\n\n${getOwnerStack(vnode)}`
					);
				}
			});
		}

		if (vnode._component === currentComponent) {
			renderCount = 0;
		}

		if (
			typeof type === 'string' &&
			(isTableElement(type) ||
				type === 'p' ||
				type === 'a' ||
				type === 'button')
		) {

View on GitHub (pinned to e881e7e838)

Solutions

  1. Render a specific property: `<div>{user.name}</div>` rather than the whole object.
  2. Serialize explicitly: `<pre>{JSON.stringify(obj, null, 2)}</pre>` for debugging.
  3. For lists, map to elements: `items.map(i => <li key={i.id}>{i.label}</li>)`.
  4. If the object is a vnode-like wrapper, ensure it has a `type` — otherwise wrap it in a component.

Example fix

// before
function Greeting({ user }) {
  return <div>Hello {user}</div>; // user is an object
}

// after
function Greeting({ user }) {
  return <div>Hello {user.name}</div>;
}
Defensive patterns

Strategy: type-guard

Validate before calling

function sanitizeChildren(children) {
  const arr = Array.isArray(children) ? children : [children];
  return arr.map(c => {
    if (c != null && typeof c === 'object' && !('type' in c) &&
        !Array.isArray(c) && !(c instanceof Date) && typeof c !== 'boolean') {
      return JSON.stringify(c);
    }
    return c;
  });
}

// before render
return <div>{sanitizeChildren(maybeObjectChild)}</div>;

Type guard

/** @param {unknown} c */
function isRenderableChild(c) {
  if (c == null || typeof c === 'boolean') return true; // ignored
  if (typeof c === 'string' || typeof c === 'number') return true;
  if (Array.isArray(c)) return c.every(isRenderableChild);
  if (typeof c === 'object') return 'type' in c; // vnode
  return false;
}

Prevention

When it happens

Trigger: Passing an object literal as a child: `<div>{ {foo:1} }</div>`; interpolating a state object directly: `<span>{user}</span>` where user is an object; returning an object from a map callback into children: `items.map(i => ({id: i.id}))`; spreading a props object into children by mistake.

Common situations: Forgetting to access a property: `<div>{user}</div>` instead of `<div>{user.name}</div>`; passing a Map/Set/Error object as a child expecting implicit toString; mock data shaped as objects fed directly into JSX; converting an API response object to children without serialization.

Related errors


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