Skip to main content

Command Palette

Search for a command to run...

React Basics: How Components, JSX, and React.createElement Really Work

Updated
•12 min read•View as Markdown
K
Backend-focused web developer and engineering student.

Table of Contents

  1. The File Structure We're Working With

  2. What is a Component? (Function as a Component)

  3. Fragments — What They Are and What Happens Without Them

  4. Can You Write Components Inside main.jsx?

  5. Using Variables Inside JSX

  6. How JSX Actually Works — The React.createElement Story

  7. The Plain JavaScript Object Approach (No JSX)

  8. Passing Multiple Children to React.createElement

  9. Vite React vs Create React App — Key Differences

  10. 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. Chai is valid. chai is 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 <>...</>?
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 MyApp function is defined but not rendered here because we're rendering <App />. If you changed root.render(<App />) to root.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: null

  • Children: 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 tag

  • Props: href and target

  • Child 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

  1. One root element per return — use <>...</> to wrap multiple elements.

  2. Component names must start with a capital letter — Chai, not chai.

  3. JSX is not HTML — it compiles to React.createElement().

  4. Curly braces {} let you inject JavaScript expressions into JSX.

  5. You can define a component anywhere — same file, different file, it doesn't matter as long as you import it correctly.

  6. React.createElement(type, props, ...children) — children can be text, variables, or other React elements.

  7. Fragments add no DOM nodes — prefer <>...</> over wrapping <div> unless you need the div.