I asked this on Spectrum chat and in the React docs repo (#2110), but haven't received a thorough explanation. It was suggested to me to cross-post here. I have a better knowledge of React at this point, but I would still like to understand the technicals regarding the juxtaposition presented below and I think it would be useful for many who are starting to learn React coming from a JavaScript background without JSX.
I haven't found the answer to this in the documentation, so perhaps someone on the team can answer and it can be added to the docs:
The homepage gives this example component:

Here is the same code (but as a function component) in JSX:
function HelloMessage (props) {
return (
<div>
Hello {props.name}
</div>
);
}
ReactDOM.render(
<HelloMessage name="Taylor" />,
document.getElementById('hello-example')
);
And here is that function component without JSX:
function HelloMessage (props) {
return React.createElement(
'div',
null,
'Hello ',
props.name
);
}
ReactDOM.render(
React.createElement(HelloMessage, {name: 'Taylor'}),
document.getElementById('hello-example')
);
In the non-JSX example, as the first argument to ReactDOM.render, it lists
React.createElement(HelloMessage, {name: 'Taylor'})
What I want to understand is this: Is it actually necessary to call React.createElement again, since it is already in the return value of the component? Is there any effective difference between that and calling the function component directly, like this?
ReactDOM.render(
HelloMessage({name: 'Taylor'}),
document.getElementById('hello-example')
);
Here is another example with child components:
function ParentComponent (props) {
return React.createElement(
React.Fragment,
null,
React.createElement(ChildComponentA, {someValue: props.someValueA}),
React.createElement(ChildComponentB, {someValue: props.someValueB}),
React.createElement(ChildComponentC, {someValue: props.someValueC})
);
}
// vs
function ParentComponent (props) {
return React.createElement(
React.Fragment,
null,
ChildComponentA({someValue: props.someValueA}),
ChildComponentB({someValue: props.someValueB}),
ChildComponentC({someValue: props.someValueC})
);
}
My understanding is that those function components like ChildComponentA and HelloMessage may not work correctly if they use React features like hooks, memoization (React.memo(HelloMessage) for example), forwardRef (React.forwardRef(ChildComponentA)` for example), and legacy context (second argument to the function).
There is some ancillary content about this pattern here: https://overreacted.io/react-as-a-ui-runtime/#recursion, however it doesn't really explain _why_ we should use createElement
What I want to understand is this: Is it actually necessary to call React.createElement again, since it is already in the return value of the component? Is there any effective difference between that and calling the function component directly, like this?
As @hamlim mentioned, the effective difference is that simply calling a component as a function wouldn't work if the component was more complex than a pure function that returned another React element. React needs the element returned from React.createElement to handle those features.
In the HelloMessage example you could technically just call it as a function, which would be equivalent to doing:
ReactDOM.render(
<div>Hello Taylor</div>,
document.getElementById("hello-example")
);
This just inlines the result of HelloMessage, which would render the same UI in the browser. Thats effectively what you're doing by calling HelloMessage as a function. Internally this would be different to React since there's no component mounted, but practically speaking the behavior is identical in this trivial example.
For the ParentComponent example, the same constraints apply. You could call the ChildComponent components as functions if they were just simple components without state or effects, and that would be the same result as just inlining their content into ParentComponent. In same cases this might be what you want, but typically not. If someone added state or effects to one of the ChildComponents, or wrapped it in React.memo or React.lazy, this would break. So use this pattern with caution.
Thanks for the responses @hamlim @aweary
Most helpful comment
Thanks for the responses @hamlim @aweary