JSX Produces Elements
JSX Produces Elements
The component in App.jsx returns something that looks like HTML sitting right inside JavaScript.
function ChapterHeading() {
return <h1>The First React Screen</h1>;
}The browser never runs that source directly. Vite transforms it into plain JavaScript which creates a React element value, React picks up that value while rendering, and React DOM may create or update a browser h1 during commit.
The order here is fixed and doesn't change.
JSX source
-> transformed JavaScript
-> React element value
-> React render work
-> DOM commit when neededKeep two things separate for this whole lesson. JSX is source syntax, nothing more. React elements are the runtime values which that syntax actually produces once transformed.
JSX is just syntax, nothing runs it directly
JavaScript on its own doesn't allow an angle-bracket element sitting inside a return statement. Something has to process this source before the browser ever sees it.
return <h1>The First React Screen</h1>;The .jsx extension tells Vite and its React plugin that this file may contain JSX, and the transform rewrites the syntax before the browser evaluates the module at all.
With the current automatic JSX runtime, the transformed output looks roughly like this call.
jsx("h1", { children: "The First React Screen" });The helper name and what exactly gets logged in development are implementation details of the tooling. What you can actually depend on is the result, a React element value whose type points to h1 and whose props hold its children.
Older JSX transforms used to produce React.createElement calls instead.
React.createElement("h1", null, "The First React Screen");Both forms end up producing React elements. One thing this changes in practice, a current Vite project doesn't need import React from "react" for JSX to compile anymore. You still import whichever named APIs a file actually uses though.
import { useState } from "react";JSX gets transformed before the browser runs anything. React elements are the JavaScript values which that transformed code produces.
Assign some JSX to a variable and render nothing at all.
const heading = <h1>The First React Screen</h1>;Run that line and check the page. No h1 shows up anywhere. The variable is holding a React element object, and just creating one changes nothing in the document itself.
Roughly, the element records its type and props, plus a key when you give it one.
type "h1"
props { children: "The First React Screen" }
key nullReact wants these objects treated as opaque values, not something you poke into. The console does show the internal fields, but those fields can change between versions, so your application code should never rely on them directly.
The element only describes what should take part in a React tree. There is no live DOM node behind it, no layout dimensions, no computed CSS, and no methods like focus.
const heading = <h1>The First React Screen</h1>;
heading.focus();That call fails, because the React element is not the browser element. For any imperative browser operation you need a ref pointing to the real DOM node instead.
Creating a React element doesn't create a DOM node. React DOM handles host creation during commit, not before.
What decides how React handles an element
When React processes an element, the first thing it looks at is the type.
const heading = <h1>ReactBook</h1>;Here the type is the string h1, a plain host element name. React DOM handles this by creating or updating the browser element for it.
An uppercase JSX element instead carries a JavaScript value as its type.
const heading = <ChapterHeading />;Here the type points at the ChapterHeading function itself. React handles this by calling the component and continuing with whatever it returns.
element type is "h1"
-> React DOM host work
element type is ChapterHeading
-> React component callSame element structure both times, but two completely different kinds of work, and the type alone decides which one happens. And notice, that type came purely from capitalization in the source.
JSX attributes become props on the produced element, one for one.
const progress = <progress value={2} max={6} />;The resulting element describes the host type progress with numeric value and max props on it.
Quoted values stay as strings.
const link = <a href="/learn">Start reading</a>;Braces hold actual JavaScript expressions.
const total = 6;
const progress = <progress value={2} max={total} />;JSX evaluates whatever is between the braces and collects the results into the new element description.
Text sitting between an opening and closing tag becomes a child.
const heading = <h1>The First React Screen</h1>;Nested elements become children too, in the same way.
const card = (
<article>
<h2>JSX Produces Elements</h2>
<p>Lesson 5 of 6</p>
</article>
);The outer article element ends up with two element children, and each of those children was itself created as its own React element value before the outer description got completed.
Written out as a tree, it looks like this.
article element
h2 element
text
p element
textThe browser may later build something similar as a host tree. But the React element tree exists first, as plain JavaScript values, before any of that DOM work happens.
These nested values become the element's children. A single child can just be one value. Several children get represented as a collection instead.
One expression, one whole tree
A function can only return one value. That's a plain JavaScript rule, and JSX works with it by letting a single expression describe a complete nested tree.
function App() {
return (
<main>
<h1>ReactBook</h1>
<p>Volume I</p>
</main>
);
}The return value is the outer main element, and that one value already carries all the nested children inside it.
Two JSX elements sitting next to each other, though, are two separate expressions.
return (
<h1>ReactBook</h1>
<p>Volume I</p>
);The transform can't turn those siblings into one returned expression, so this fails with a syntax error. You have to wrap them in a parent.
return (
<main>
<h1>ReactBook</h1>
<p>Volume I</p>
</main>
);Fragments can group siblings without adding any extra DOM wrapper. Use a real HTML parent whenever document semantics, browser behaviour, or CSS layout actually need one.
There's another rule worth being careful of here. JavaScript can quietly insert a semicolon right after a bare return followed by a newline, and this rule clashes badly with JSX.
function App() {
return
<h1>ReactBook</h1>;
}The function returns undefined here, and the JSX below never even gets reached. No error points you to the real problem either, the component output is simply empty.
So keep the opening parenthesis on the same line as return.
function App() {
return (
<h1>ReactBook</h1>
);
}The parentheses group everything into one JavaScript expression, and multiline formatting becomes safe again.
Don't put a newline right after return before a JSX expression. JavaScript's automatic semicolon insertion can quietly end the statement there.
Short one-line returns don't need any parentheses though.
function Logo() {
return <img src="/react-mark.svg" alt="" />;
}Every JSX tag also needs some closing form, host elements and components both.
<h1>ReactBook</h1>An element with no children can close itself.
<img src="/react-mark.svg" alt="" />HTML allows certain end tags to be left out. JSX follows a JavaScript expression grammar instead, and it needs explicit closing everywhere, no exceptions.
Component elements follow this exact same syntax.
<LessonProgress />If a closing tag is missing, the file simply fails to transform. Vite will point at the syntax location, since no React element value can come out of invalid JSX.
JSX values are not HTML strings, and they don't change after creation
Compare two values which look nearly identical in the source. The first one here is a plain string.
const heading = "<h1>ReactBook</h1>";Render that string as a React child and the page shows the angle brackets as literal text on screen.
This next one is a React element.
const heading = <h1>ReactBook</h1>;Render the element and React DOM produces an actual h1 host element.
React refuses to parse ordinary strings as HTML, and this refusal is what keeps text content from turning into executable markup by accident. The separate dangerouslySetInnerHTML prop skips this normal text handling, and its name is basically an honest warning about what it does. Use it only with HTML you trust, or HTML that's been properly sanitized.
Don't convert JSX into HTML strings for normal rendering. Element values keep React's component and prop model intact.
Create an element from a variable, then change the variable afterward.
let completed = 1;
const progress = <p>{completed} lesson complete</p>;The expression read completed at the moment the element got created. Assigning a new number afterward doesn't reach back into that already-created element.
completed = 2;Describing the next result needs a brand new element.
const nextProgress = <p>{completed} lessons complete</p>;Components do exactly this on every single render. Current props and state produce a fresh element description, React compares this next tree against its current tree, and React DOM commits whatever difference it finds.
Don't try to mutate an element after creating it either.
progress.props.children = "Changed";React treats element descriptions as immutable values. Development builds can even freeze elements and their props shallowly, just to catch mutation attempts like this one.
Creating an element only produces a small JavaScript record. No layout, no paint, no DOM insertion happens at that point.
const first = <LessonProgress completed={1} />;
const second = <LessonProgress completed={2} />;Neither of these lines touches the page at all until the element becomes part of content which some root or another component actually renders.
This low cost is genuinely useful. Conditional code can build and pick between elements freely before React ever commits anything.
const status = complete
? <p>Chapter complete</p>
: <p>Continue reading</p>;Both branches here are ordinary expressions producing element values. Only the one which actually gets chosen ends up in the rendered tree.
Putting it together in the App component
If you want to see an element with your own eyes, log one.
const heading = <h1>ReactBook</h1>;
console.log(heading);The console shows an object full of React's internal fields, and you can confirm for yourself that no DOM node is hiding inside it anywhere.
Don't read those private-looking fields, and don't copy the console's object format into your application code. Stick to JSX, createElement, cloneElement in its limited supported cases, and the regular React APIs for creating and working with elements.
And remove the log once you're done with it. Strict Mode and later renders can print it far more often than the visible page actually changes.
Now let's finish the current App using the small components already sitting in App.jsx.
function ChapterHeading() {
return <h1>The First React Screen</h1>;
}
function LessonProgress() {
return <p>0 of 6 lessons complete</p>;
}Return one nested element tree from App.
export default function App() {
return (
<main>
<ChapterHeading />
<LessonProgress />
</main>
);
}Here's how the values move, in order. JSX creates an App element in main.jsx, React calls App, the JSX inside App produces a main element with component children in it, React then calls each of those child components, their JSX in turn produces h1 and p elements, and finally React DOM commits the corresponding DOM nodes to the page.
Before moving to the next lesson, keep these four names separate in your head, because mixing them up hides which operation actually produced which value.
JSX is source syntax
a React element is a JavaScript description
a component returns renderable content
a DOM element is a browser node
commit is when React DOM applies host changes