createElement
Creates a React element without JSX, taking a type, a props object and any number of children.
const element = createElement(type, props, ...children)Parameters
| Parameter | Type | Description |
|---|---|---|
$type | string | React.ComponentType | A tag name string such as 'div' or 'span', or a React component — a function, a class, or a special type such as Fragment. |
$props | object | null | The props object, or null for no props. `key` and `ref` are extracted rather than left on `element.props`. |
$...childrenoptional | ...React.ReactNode | Zero 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
.jsfile 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
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.
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.
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.
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