React Basics: How Components, JSX, and React.createElement Really Work
Table of Contents
The File Structure We're Working With
What is a Component? (Function as a Component)
Fragments — What They Are and What Happens Without Them
Can You Write Components Inside main.jsx?
Using Variables Inside JSX
How JSX Actually Works — The React.createElement Story
The Plain JavaScript Object Approach (No JSX)
Passing Multiple Children to React.createElement
Vite React vs Create React App — Key Differences
Summary Cheatsheet
1. The File Structure We're Working With
Before I explain anything, let me show you the three files we are dealing with and what job each one does.
src/
├── main.jsx ← Entry point. This is where React starts.
├── App.jsx ← Root component. The parent of all components.
└── Chai.jsx ← A child component. Imported and used inside App.
Think of it like a tree:
main.jsx
└── App
└── Chai
main.jsx is the one that talks to the browser's actual DOM. App is your top-level React component. Chai is a smaller piece that App uses.
2. What is a Component? (Function as a Component)
In React, a component is simply a JavaScript function that returns JSX (HTML-like code).
Here is Chai.jsx:
function Chai() {
return (
<h2>chai in react</h2>
);
}
export default Chai;
And here is App.jsx importing and using it:
import Chai from "./Chai";
function App() {
return (
<>
<Chai />
</>
);
}
export default App;
Rules you must follow when writing a component:
The function name must start with a capital letter.
Chaiis valid.chaiis not — React will treat it as a plain HTML tag.The function must return JSX (or
null).You use it like an HTML tag:
<Chai />— self-closing is fine when there's no children.
3. Fragments — What They Are and What Happens Without Them
What is a Fragment?
A Fragment (<>...</>) is an invisible wrapper. It lets you return multiple elements from a component without adding an extra DOM node.
function App() {
return (
<>
<Chai />
<h1>Hello</h1>
</>
);
}
The <> and </> are shorthand for <React.Fragment> and </React.Fragment>.
What Happens If You Don't Use a Fragment?
This is one of the most important rules in React:
A component can only return ONE root element.
If you try to return two sibling elements without a wrapper, you get an error.
❌ This Will Crash — No Wrapper
function App() {
return (
<Chai />
<h1>Hello</h1>
);
}
Output / Error:
SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag.
Did you want a JSX fragment <>...</>?
✅ Fix 1 — Use a Fragment (Recommended)
function App() {
return (
<>
<Chai />
<h1>Hello</h1>
</>
);
}
DOM Output:
<h2>chai in react</h2>
<h1>Hello</h1>
No extra wrapper <div> in the DOM. Clean.
✅ Fix 2 — Wrap in a <div> (Adds extra node)
function App() {
return (
<div>
<Chai />
<h1>Hello</h1>
</div>
);
}
DOM Output:
<div>
<h2>chai in react</h2>
<h1>Hello</h1>
</div>
This works, but you get an extra <div> in your HTML. For small projects, it doesn't matter. But when you have deep nesting, it creates "div soup" — too many useless wrappers.
Does the Error Differ Between Vite React and Create React App (CRA)?
Yes, slightly. But the root rule is the same — one parent element per return.
| Tool | Error Message Style | Error Shown At |
|---|---|---|
| Vite + React | Shows in browser overlay (red screen), very clear | Build time + Runtime |
| Create React App | Shows in browser console and overlay | Compile time |
In Vite React:
Vite uses esbuild under the hood. It processes JSX very fast and shows you a clear overlay in the browser with the exact line number.
[plugin:vite:react-babel] Adjacent JSX elements must be wrapped
in an enclosing tag. (5:4)
In Create React App (CRA):
CRA uses Babel with Webpack. The error looks like this in the terminal and browser:
Failed to compile.
./src/App.js
Line 5:5: Parsing error: Adjacent JSX elements must be
wrapped in an enclosing tag. Did you want a JSX fragment <>...</>?
The fix is identical in both. Use <>...</> or a <div>.
4. Can You Write Components Inside main.jsx?
Yes, absolutely. React doesn't care which file your component is in. What matters is that it's a valid function returning JSX.
Here's the complete main.jsx from your code, and I've shown how you can define MyApp right there in the same file:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
// ✅ You can define a component right here in main.jsx
function MyApp() {
return (
<div>
<h1>Custom App | chai</h1>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Browser Output:
chai in react ← from Chai component inside App
The
MyAppfunction is defined but not rendered here because we're rendering<App />. If you changedroot.render(<App />)toroot.render(<MyApp />), you'd see "Custom App | chai" instead.
Why separate files then?
As your project grows, keeping every component in one file becomes messy. So we split them into their own files — one component per file. But technically, there's nothing stopping you from writing everything in main.jsx.
5. Using Variables Inside JSX
You can embed any JavaScript variable inside JSX using curly braces {}.
function App() {
const username = "chai aur code";
return (
<>
<Chai />
<h1>chai aur react {username}</h1>
<p>test para</p>
</>
);
}
Browser Output:
chai is ready
chai aur react chai aur code
test para
The {username} gets replaced with the actual value of the variable at render time.
You can put any valid JavaScript expression inside {}:
<h1>{2 + 2}</h1> // Output: 4
<h1>{"hello".toUpperCase()}</h1> // Output: HELLO
<h1>{username}</h1> // Output: chai aur code
6. How JSX Actually Works — The React.createElement Story
This is the part most beginners skip, but I want you to understand it deeply.
When you write this JSX:
function Chai() {
return (
<h2>chai in react</h2>
);
}
You think this is HTML. It's not. JSX is not HTML. It is syntactic sugar that gets compiled down to plain JavaScript function calls.
Under the hood, Babel (or esbuild in Vite) transforms your JSX into:
function Chai() {
return React.createElement("h2", null, "chai in react");
}
React.createElement — The Syntax
React.createElement(type, props, ...children)
| Argument | What It Is | Example |
|---|---|---|
type |
The HTML tag or Component name | "h2", "a", App |
props |
An object of attributes (like href, className) |
{ href: "https://..." } |
...children |
Text or other elements inside | "chai in react" |
Example 1 — A Simple Tag
JSX you write:
<h2>chai in react</h2>
What it becomes:
React.createElement("h2", null, "chai in react")
Type:
"h2"(string, because it's a native HTML tag)Props:
null(no attributes)Children:
"chai in react"(plain text)
DOM Output:
<h2>chai in react</h2>
Example 2 — A Tag With Attributes
JSX you write:
<a href="https://google.com" target="_blank">Visit google</a>
What it becomes:
React.createElement(
"a",
{ href: "https://google.com", target: "_blank" },
"Visit google"
)
DOM Output:
<a href="https://google.com" target="_blank">Visit google</a>
Example 3 — A Component (Capital Letter)
JSX you write:
<Chai />
What it becomes:
React.createElement(Chai, null)
Type:
Chai— the actual function reference, not a string.Props:
nullChildren: none
React sees a capital letter → it knows this is a component → it calls the Chai function → uses what it returns.
Where is React Imported in Older Code?
In older React (before version 17), you had to import React at the top of every file that used JSX — even if you never used the React variable directly:
import React from 'react';
This was needed because JSX compiled to React.createElement(...), so React had to be in scope.
From React 17+, this is no longer necessary. The new JSX transform handles it automatically. That's why in Vite + React projects you don't always see that import.
7. The Plain JavaScript Object Approach (No JSX)
In your main.jsx, there's a commented-out block:
// const ReactElement = {
// type: 'a',
// props: {
// href: 'https://google.com',
// target: '_blank'
// },
// children: 'Click me to visit google'
// }
This is showing you what React elements look like internally as a plain JavaScript object. When React processes React.createElement(...), it produces an object that looks roughly like this.
However — and this is important — you cannot pass this raw object directly to root.render(). React will not know what to do with it. You must use React.createElement() or JSX.
The reason this is commented out is to teach you that React elements are just objects, not magic. React reads this object tree and figures out what to put in the DOM.
8. Passing Multiple Children to React.createElement
Here's the most interesting part of your code:
const anotherElement = (
<a href="https://google.com" target="_blank">Visit google</a>
);
const anotherUser = "chai aur react";
const reactElement = React.createElement(
'a',
{ href: 'https://google.com', target: '_blank' },
'click me to visit google',
anotherElement
);
ReactDOM.createRoot(document.getElementById('root')).render(
reactElement
);
Let me break this down piece by piece.
Step 1 — anotherElement is a JSX variable
const anotherElement = (
<a href="https://google.com" target="_blank">Visit google</a>
);
This is valid JSX stored in a variable. It compiles to:
const anotherElement = React.createElement(
'a',
{ href: "https://google.com", target: "_blank" },
"Visit google"
);
So anotherElement is now a React element object.
Step 2 — React.createElement With Multiple Children
const reactElement = React.createElement(
'a',
{ href: 'https://google.com', target: '_blank' },
'click me to visit google', // child 1 — plain text
anotherElement // child 2 — another React element
);
Type:
'a'→ an anchor tagProps:
hrefandtargetChild 1: The text
"click me to visit google"Child 2:
anotherElement— which is another<a>tag
DOM Output:
<a href="https://google.com" target="_blank">
click me to visit google
<a href="https://google.com" target="_blank">Visit google</a>
</a>
An <a> tag inside another <a> tag is technically invalid HTML (nested anchors), but this example is purely for learning purposes — to show you that you can nest elements using React.createElement by passing them as additional arguments.
Step 3 — Rendering
ReactDOM.createRoot(document.getElementById('root')).render(
reactElement
);
ReactDOM.createRoot() finds the <div id="root"> in your index.html and React takes over that div, rendering reactElement into it.
9. Vite React vs Create React App — Key Differences
| Feature | Vite + React | Create React App (CRA) |
|---|---|---|
| Speed | Very fast (uses esbuild) | Slower (uses Webpack) |
| Entry file | main.jsx |
index.js |
import React |
Not always needed | Required in older versions |
root.render() wrapping |
No <React.StrictMode> by default |
Wraps in <React.StrictMode> |
| JSX transform | New (automatic) | New since v17 |
| Fragment requirement | Same — one root per return | Same — one root per return |
CRA — Default main.jsx (index.js)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Vite — Default main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
Both use ReactDOM.createRoot() — the modern React 18 API. Older CRA projects used ReactDOM.render() (deprecated).
10. Summary Cheatsheet
JSX → React.createElement()
------------------------------------------------------------
<h2>chai</h2> → React.createElement("h2", null, "chai")
<a href="...">text</a> → React.createElement("a", {href:"..."}, "text")
<Chai /> → React.createElement(Chai, null)
<h1>{username}</h1> → React.createElement("h1", null, username)
<><Chai /><h1>hi</h1></> → React.createElement(Fragment, null,
React.createElement(Chai, null),
React.createElement("h1", null, "hi"))
Rules to Always Remember
One root element per return — use
<>...</>to wrap multiple elements.Component names must start with a capital letter —
Chai, notchai.JSX is not HTML — it compiles to
React.createElement().Curly braces
{}let you inject JavaScript expressions into JSX.You can define a component anywhere — same file, different file, it doesn't matter as long as you import it correctly.
React.createElement(type, props, ...children)— children can be text, variables, or other React elements.Fragments add no DOM nodes — prefer
<>...</>over wrapping<div>unless you need the div.

