createElement

Creates a React element without JSX, taking a type, a props object and any number of children.

Signature
const element = createElement(type, props, ...children)

Parameters

ParameterTypeDescription
$typestring | React.ComponentTypeA tag name string such as 'div' or 'span', or a React component — a function, a class, or a special type such as Fragment.
$propsobject | nullThe props object, or null for no props. `key` and `ref` are extracted rather than left on `element.props`.
$...childrenoptional...React.ReactNodeZero or more child nodes: elements, strings, numbers, portals, null, undefined, booleans, or arrays of those.

Return value

React elementA React element object with `type`, `props`, `ref` and `key` fields. It is a plain description of what to render, not a rendered node.

Errors

React throws while rendering if `type` is undefined — usually a misspelled or circular import — not at the moment the element is created.

Changelog

  • PHP 19.0`ref` is passed to function components as an ordinary prop, so `element.props.ref` now exists for them; reading `element.ref` warns.
  • PHP 19.0`defaultProps` is no longer applied to function components created this way. Class components still honour it.
  • PHP 19.0`createFactory` was removed; call this function directly instead.

Every JSX tag compiles to one of these calls: <h1 className="title">Hi</h1> becomes createElement('h1', {className: 'title'}, 'Hi'). Because JSX is the compiled-away form, you almost never write this by hand.

When to reach for it

  • A build step with no JSX transform — a plain .js file loaded in the browser, a snippet inside a CMS, a tiny script.
  • Code that builds a tree from data where the element type itself is dynamic and JSX would be awkward.
  • Library code that wraps or forwards to an arbitrary component type.

What trips people up

Capitalisation is meaningful in JSX but not here. createElement('mycomponent') creates an unknown HTML tag; you must pass the component value, not its name as a string. Passing an array of children as separate arguments defeats React's key warning, so pass a dynamic list as a single array argument instead.

The returned object is a description, not a DOM node — it has no .focus(), and nothing has rendered yet. Treat it and its props as immutable; React shallow-freezes both in development, so mutating element.props silently fails.

Example

Try it yourself
Loading editor…
Loading React…

When to use it

  • Write React in a plain script tag with no build step or JSX transform
  • Build an element whose tag is chosen at runtime from a data field
  • Wrap an arbitrary component type inside a library helper

Frequently asked questions

Do I still need to import React to use createElement?

You import `createElement` itself, but with the modern JSX transform (React 17+) you no longer need `import React from 'react'` just to write JSX. Named imports like `import { createElement } from 'react'` are all that is required.

What is the difference between createElement and rendering a component?

createElement only builds a description object; it never calls your component function. React calls the component later, when it renders the element into the tree.

Why does React warn about missing keys when I pass children as separate arguments?

It does not — that is the problem. Keys are only checked for children passed as a single array, so pass a dynamic list as one array argument rather than spreading it into separate arguments.

More examples

The same markup, both ways

Both components render identical output. The second is the literal result of running the first through the JSX transform, which is why props become the second argument and children become the rest.

Example · jsx
import { createElement } from 'react';

// JSX
function JsxVersion() {
  return (
    <section className="card">
      <h1>Title</h1>
      <p>Body text</p>
    </section>
  );
}

// Exactly what the JSX above compiles to
function CompiledVersion() {
  return createElement(
    'section',
    { className: 'card' },
    createElement('h1', null, 'Title'),
    createElement('p', null, 'Body text')
  );
}

export default function Both() {
  return (
    <div>
      <JsxVersion />
      <CompiledVersion />
    </div>
  );
}

Choosing the tag at runtime

Each entry renders with a different HTML tag taken from data. In JSX the same thing needs a capitalised variable (`const Tag = block.tag; <Tag />`), because a lowercase JSX name is always treated as a literal tag string.

Example · jsx
import { createElement } from 'react';

const blocks = [
  { tag: 'h3', text: 'Chapter one' },
  { tag: 'p', text: 'A paragraph of body copy.' },
  { tag: 'blockquote', text: 'A pulled quote.' },
];

function Block({ tag, text }) {
  // A string type produces that HTML tag
  return createElement(tag, null, text);
}

export default function Article() {
  return (
    <article>
      {blocks.map((block, i) => (
        <Block key={i} tag={block.tag} text={block.text} />
      ))}
    </article>
  );
}

Inspecting the object it returns

Shows that the return value is a plain object: `type` is the string 'a', the children argument became `props.children`, and `key` was pulled out of props onto the element itself.

Example · jsx
import { createElement } from 'react';

const element = createElement(
  'a',
  { href: '/docs', key: 'link-1' },
  'Read the docs'
);

export default function Inspect() {
  return (
    <ul>
      <li>type: {JSON.stringify(element.type)}</li>
      <li>props.href: {element.props.href}</li>
      <li>props.children: {element.props.children}</li>
      <li>key: {element.key}</li>
    </ul>
  );
}

Discussion

  • Be the first to comment on this lesson.