preactjs/preact · critical · Error

Expected a valid HTML node as a second argument to render. R

Error message

Expected a valid HTML node as a second argument to render.	Received ${parentNode} instead: render(<${componentName} />, ${parentNode});

What it means

Thrown from the options._root hook when the parentNode is truthy but its nodeType is not one of ELEMENT_NODE (1), DOCUMENT_FRAGMENT_NODE (11), or DOCUMENT_NODE (9). Preact can only mount into a real DOM node, a fragment, or the document itself; anything else (text node, comment, attribute node, a plain JS object) is rejected with a message echoing the component name and the bad value.

Source

Thrown at debug/src/debug.js:134

				'Undefined parent passed to render(), this is the second argument.\n' +
					'Check if the element is available in the DOM/has the correct id.'
			);
		}

		let isValid;
		switch (parentNode.nodeType) {
			case ELEMENT_NODE:
			case DOCUMENT_FRAGMENT_NODE:
			case DOCUMENT_NODE:
				isValid = true;
				break;
			default:
				isValid = false;
		}

		if (!isValid) {
			let componentName = getDisplayName(vnode);
			throw new Error(
				`Expected a valid HTML node as a second argument to render.	Received ${parentNode} instead: render(<${componentName} />, ${parentNode});`
			);
		}

		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)}`

View on GitHub (pinned to e881e7e838)

Solutions

  1. Ensure the second argument is a single Element: use document.querySelector('#app') (returns Element|null) rather than a NodeList or wrapper.
  2. If using jQuery, unwrap with [0]: render(<App/>, $('#app').get(0)).
  3. For shadow DOM or fragments, pass the actual ShadowRoot (nodeType 11) or a DocumentFragment created via document.createDocumentFragment().
  4. Log parentNode.nodeType before render — it must be 1, 9, or 11.

Example fix

// before
import $ from 'jquery';
render(<App />, $('#app'));

// after
import $ from 'jquery';
render(<App />, $('#app').get(0));
Defensive patterns

Strategy: validation

Validate before calling

function resolveMountNode(selectorOrEl) {
  const el = typeof selectorOrEl === 'string'
    ? document.querySelector(selectorOrEl)
    : selectorOrEl;
  if (!el || ![1, 9, 11].includes(el.nodeType)) {
    throw new Error(`Invalid mount target: ${el}`);
  }
  return el;
}

render(<App />, resolveMountNode('#app'));

Type guard

/** @param {unknown} n */
function isPreactMountTarget(n) {
  if (n == null || typeof n !== 'object') return false;
  const t = /** @type {Node} */ (n).nodeType;
  return t === 1 || t === 9 || t === 11;
}

Prevention

When it happens

Trigger: Passing a text node (document.createTextNode('x')), a comment node, or an attribute node as the render target; passing a jQuery-wrapped collection or a plain object that lacks nodeType; passing the result of querySelector that unexpectedly returned a non-element; passing document.doctype or a processing instruction.

Common situations: Mixing jQuery and Preact and handing over a $() collection instead of $()[0]; test utilities that pass a detached string-HTML fragment object; SSR-to-client handoff where the target is a serialized node rather than a live element; code written for React that passes a document fragment created offscreen.

Related errors


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