React Element vs Component

 by Robin Wieruch
 - Edit this Post

React Elements, Components, and Instances are different terms in React which work closely together. This guide will walk you through all three terms and explain them step by step. We will start off with the following code snippet as example:

const App = () => {
return <p>Hello React</p>;
};

A React component is literally the declaration of a component as we see it in the previous code snippet. In our case, it's a but it could be any of React component (e.g. React Class Component) too.

In the case of a function component, it is declared as a JavaScript function which returns React's JSX. While more complex JSX is a mixture of HTML and JavaScript, here we are dealing with a simple example which returns just one HTML element with an inner content.

(props) => JSX

We can extract a component from another component and render it the following way. Rendering a component happens whenever we use this component as a React element with angle brackets (e.g. <Greeting />) in another component:

const Greeting = ({ text }) => {
return <p>{text}</p>;
};
const App = () => {
return <Greeting text="Hello React" />;
};

We can render a component as React element multiple times too. Whenever a component gets rendered as element, we create an instance of this component:

const Greeting = ({ text }) => {
return <p>{text}</p>;
};
const App = () => {
return (
<>
<Greeting text="Hello Instance 1 of Greeting" />
<Greeting text="Hello Instance 2 of Greeting" />
</>
);
};

While a React component is declared once, it can be used multiple times as a React element in JSX. When it is used, it becomes an instance of the component and lives in React's component tree. Essentially that's the explanation of React components, element, and instance in a nutshell. However, in order to understand everything on a deeper level, we need to understand how React displays HTML with JSX.

React Elements in Depth

Let's take one step back and start with a simple example again:

const App = () => {
return <p>Hello React</p>;
};

Whenever a React component gets called (rendering), React calls its React.createElement() method internally which returns the following object:

console.log(App());
// {
// $$typeof: Symbol(react.element)
// "type": "p",
// "key": null,
// "ref": null,
// "props": {
// "children": "Hello React"
// },
// "_owner": null,
// "_store": {}
// }

Focus your attention on the type and props properties of this object: While the type represents the actual HTML element, the props are all HTML attributes (plus the inner content, read: children) which are passed to this HTML element.

When looking at the paragraph HTML element from above, you can see that no attributes are passed to it. However, React treats children as pseudo HTML attribute whereas children represents everything that's rendered between the HTML tag. This fact becomes clearer when adding an attribute to the paragraph HTML element:

const App = () => {
return <p className="danger">Hello React</p>;
};
console.log(App());
// {
// $$typeof: Symbol(react.element)
// "type": "p",
// "key": null,
// "ref": null,
// "props": {
// "children": "Hello React",
// "className": "danger"
// },
// "_owner": null,
// "_store": {}
// }

Essentially React translates all HTML attributes to React props in addition to adding the inner content as children property.

As mentioned, React's createElement() method is called internally. Therefore we could use it as replacement for the returned JSX (for the sake of learning). React's createElement method takes a type, props, and children as arguments. We provide the HTML tag 'p' as first argument, the props as an object with the className as second argument, and the children as third argument:

const App = () => {
// return <p className="danger">Hello React</p>;
return React.createElement(
'p',
{ className: 'danger' },
'Hello React'
);
};

See how the method call does not reflect 1:1 the returned object where the children are part of the props object. Instead, when calling React's createElement() method, the children are provided separately as argument. However, since children are treated as props, we could also pass them in the second argument:

const App = () => {
// return <p className="danger">Hello React</p>;
return React.createElement(
'p',
{
className: 'danger',
children: 'Hello React'
}
);
};

As default children are used as third argument though. The following example shows how a React component, which renders a HTML tree as JSX, translates into React element(s) with React's createElement() method. The important lines are highlighted:

const App = () => {
return (
<div className="container">
<p className="danger">Hello React</p>
<p className="info">You rock, React!</p>
</div>
);
};
console.log(App());
// {
// $$typeof: Symbol(react.element)
// "type": "div",
// "key": null,
// "ref": null,
// "props": {
// "className": "container",
// "children": [
// {
// $$typeof: Symbol(react.element)
// "type": "p",
// "key": null,
// "ref": null,
// "props": {
// "className": "danger",
// "children": "Hello React"
// },
// "_owner": null,
// "_store": {}
// },
// {
// $$typeof: Symbol(react.element)
// "type": "p",
// "key": null,
// "ref": null,
// "props": {
// className: "info",
// children: "You rock, React!"
// },
// "_owner": null,
// "_store": {}
// }
// ]
// },
// "_owner": null,
// "_store": {}
// }

Again internally all JSX gets translated with React's createElement() method. While we return one element as object, it has multiple inner elements as children in this example. This becomes more obvious when calling the method for creating the element ourselves:

const App = () => {
// return (
// <div className="container">
// <p className="danger">Hello React</p>
// <p className="info">You rock, React!</p>
// </div>
// );
return React.createElement(
'div',
{
className: 'container',
},
[
React.createElement(
'p',
{ className: 'danger' },
'Hello React'
),
React.createElement(
'p',
{ className: 'info' },
'You rock, React!'
),
]
);
};

Working with multiple components does not change this aggregation of HTML elements. Take the following code snippet where we extracted the paragraph HTML element as standalone React component:

const Text = ({ className, children }) => {
return <p className={className}>{children}</p>;
};
const App = () => {
return (
<div className="container">
<Text className="danger">Hello React</Text>
<Text className="info">You rock, React!</Text>
</div>
);
};

If you traverse through the underlying HTML elements yourself, you will notice that it didn't change from before. Only in React land we have extracted it as reusable component. So calling React's createElement() method would look the same as before.

As a little extra learning here, we can also mix both worlds by using the extracted component in React's createElement() method call as first argument:

const Text = ({ className, children }) => {
return <p className={className}>{children}</p>;
};
const App = () => {
// return (
// <div className="container">
// <Text className="danger">Hello React</Text>
// <Text className="info">You rock, React!</Text>
// </div>
// );
return React.createElement(
'div',
{
className: 'container',
},
[
React.createElement(
Text,
{ className: 'danger' },
'Hello React'
),
React.createElement(
Text,
{ className: 'info' },
'You rock, React!'
),
]
);
};

To make the example complete though, we would have to replace the child component's JSX with React's createElement() too:

const Text = ({ className, children }) => {
return React.createElement('p', { className }, children);
};
const App = () => {
return React.createElement(
'div',
{
className: 'container',
},
[
React.createElement(
Text,
{ className: 'danger' },
'Hello React'
),
React.createElement(
Text,
{ className: 'info' },
'You rock, React!'
),
]
);
};

This way, we are only working the React's createElement() method and not JSX anymore while still being able to extract components from each other. This is absolutely not recommended though and just illustrates how React creates elements under the hood from its JSX.

What we have learned in this section is that not only <Text /> or <Greeting /> are React elements, but also all other HTML elements in JSX which get translated in a React createElement() call. Essentially under the hood we work with React elements to render the desired JSX. Because we want to use declarative over imperative programming in React, we use JSX as the default and not React's createElement() method.

Call a React Function Component

What's the actual difference between calling a React function component vs using it as React element? In the previous code snippets, we have called function components for returning their output from React's createElement() method. How does the output differ when using it as React element instead:

const App = () => {
return <p>Hello React</p>;
};
console.log(App());
// {
// $$typeof: Symbol(react.element),
// "type": "p",
// "key": null,
// "ref": null,
// "props": {
// "children": "Hello React"
// },
// "_owner": null,
// "_store": {}
// }
console.log(<App />);
// {
// $$typeof: Symbol(react.element),
// "key": null,
// "ref": null,
// "props": {},
// "type": () => {…},
// "_owner": null,
// "_store": {}
// }

The output slightly differs. When using a React component as element instead of calling it, we get a type function which encloses all the function components implementation details (e.g. children, hooks). The props are all the other HTML attributes that are passed to the component.

console.log(<App className="danger" />);
// {
// $$typeof: Symbol(react.element),
// "key": null,
// "ref": null,
// "props": {
"className": "danger"
// },
// "type": () => {…},
// "_owner": null,
// "_store": {}
// }

What does it mean for a real React application that the type becomes a function and isn't a string anymore? Let's check this out with an example which demonstrates why we shouldn't call a React function component. First, we use a components as intended by using angle brackets:

const Counter = ({ initialCount }) => {
const [count, setCount] = React.useState(initialCount);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<div>{count}</div>
</div>
);
};
const App = () => {
return (
<div>
<Counter initialCount={42} />
</div>
);
};

With our learnings from before, we would assume that calling a function component instead of using it as React element should just work out of the box. Indeed it does as we can see next:

const App = () => {
return (
<div>
{Counter({ initialCount: 42 })}
</div>
);
};

But let's explore why we should not call a React function component. We will use a for the rendered child component which can be toggled with a button click:

const App = () => {
const [isVisible, setVisible] = React.useState(true);
return (
<div>
<button onClick={() => setVisible(!isVisible)}>Toggle</button>
{isVisible ? Counter({ initialCount: 42 }) : null}
</div>
);
};

When we toggle the child component to invisible, we get an error saying: "Uncaught Error: Rendered fewer hooks than expected." If you have worked with React Hooks before, you may know that this should be possible though, because the hook is allocated in the child component (here: Counter), which means that if this component unmounts, because it's conditionally rendered, the hook should be removed without any errors. Only if a mounted component changes its number of hooks (here: App), it should crash.

Continue Reading:

But indeed it crashes because a mounted component (here: App) changes its number of hooks. Because we are calling the child component (here: Counter) as function, React does not treat it as an actual instance of a React component. Instead it just places all implementation details (e.g. hooks) of the child component directly in its parent component. Because the implementation of the hook disappears in a mounted component (here: App) due to the conditional rendering, the React application crashes.

Essentially the current code is the same as the following, because the child component is not treated as a standalone instance of a component:

const App = () => {
const [isVisible, setVisible] = React.useState(true);
return (
<div>
<button onClick={() => setVisible(!isVisible)}>Toggle</button>
{isVisible
? (() => {
const [count, setCount] = React.useState(42);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<div>{count}</div>
</div>
);
})()
: null}
</div>
);
};

This violates the rules of hooks, because a React Hook cannot be used conditionally in a component.

Continue Reading:

We can fix this error by telling React about this React component which in return is treated as an actual instance of a component. Then it can allocate the implementation details within this instance of the component. When the conditional rendering kicks in, the component just unmounts and with it its implementation details (e.g. hooks):

const App = () => {
const [isVisible, setVisible] = React.useState(true);
return (
<div>
<button onClick={() => setVisible(!isVisible)}>Toggle</button>
{isVisible ? <Counter initialCount={42} /> : null}
</div>
);
};

Here you can see why instances of React components make sense. Each instance allocates its own implementation details without leaking it to other components. Therefore we are using React elements instead of calling a function component in JSX. In conclusion, a function that returns JSX might not be a component. It depends on how it is used.

React Elements vs Components

Let's summarize React Elements and Components: While a React Component is the one time declaration of a component, it can be used once or multiple times as React Element in JSX. In JSX it can be used with angle brackets, however, under the hood React's createElement method kicks in to create React elements as JavaScript object for each HTML element.

const Text = ({ children }) => {
console.log('I am calling as an instance of Text');
return <p>{children}</p>;
};
console.log('I am a component', Text);
const App = () => {
console.log('I am calling as an instance of App');
const paragraphOne = <p>You rock, React!</p>;
const paragraphTwo = <Text>Bye!</Text>;
console.log('I am an element:', paragraphOne);
console.log('I am an element too:', paragraphTwo);
return (
<div>
<p>Hello React</p>
{paragraphOne}
{paragraphTwo}
</div>
);
};
console.log('I am a component', App);
console.log('I am an element', <App />);
console.log('I am an element', <p>too</p>);

Keep reading about 

A brief step by step tutorial on how to install and use Prettier in VS Code (Visual Studio Code) . Prettier is an opinionated code formatter which ensures one unified code format. It can be used in…

A brief step by step tutorial on how to install and use ESLint in VS Code (Visual Studio Code) . ESLint supports you and teams to follow a common code style in your project. It can be used in VS…

The Road to React

Learn React by building real world applications. No setup configuration. No tooling. Plain React in 200+ pages of learning material. Learn React like 50.000+ readers.

Get it on Amazon.