React Components and JSX Explained
A thorough beginner's guide to React components and JSX — how JSX compiles, the rules that catch beginners, composition, conditional rendering, and rendering lists with keys.
What you'll learn
- ✓What a React component really is under the hood
- ✓How JSX compiles to JavaScript, and the rules that follow from that
- ✓The difference between an element and a component
- ✓How to compose components and render children
- ✓Conditional rendering and list rendering with keys
Prerequisites
- •A working React project — see Set Up a React App with Vite
- •Comfort with arrays, objects, and arrow functions
A React component is the unit you spend almost all of your time writing. This post covers what a component actually is, how JSX works, the everyday rules that catch beginners, and the composition patterns used in every real codebase.
What a component is
A component is a JavaScript function whose name starts with a capital letter and which returns markup. That is the entire rule.
function Welcome() {
return <h1>Welcome</h1>;
}
The capital letter is not a stylistic choice. It is how React’s JSX compiler distinguishes between a built-in HTML tag and one of your components.
<welcome /> // treated as an unknown HTML tag
<Welcome /> // treated as your component
Forgetting to capitalise is the single most common mistake on the first day of React.
What JSX actually is
JSX is not HTML and not a string. It is a syntax extension that compiles to plain JavaScript function calls. This:
const element = <h1 className="title">Hello</h1>;
Compiles to roughly this:
const element = React.createElement('h1', { className: 'title' }, 'Hello');
You will almost never write createElement by hand. But understanding that JSX is a function call explains every quirk you will encounter.
JSX rules that follow from being JavaScript
Because JSX is JavaScript, a handful of rules apply that catch beginners.
1. Return exactly one root element.
A function returns one value. JSX is no exception.
// Error
function Bad() {
return (
<h1>Hello</h1>
<p>World</p>
);
}
// Correct
function Good() {
return (
<div>
<h1>Hello</h1>
<p>World</p>
</div>
);
}
If you do not want an extra wrapping <div>, use a fragment:
function Good() {
return (
<>
<h1>Hello</h1>
<p>World</p>
</>
);
}
A fragment groups children without adding a DOM node.
2. class becomes className. for becomes htmlFor.
Both class and for are reserved words in JavaScript, so JSX renames them.
<label htmlFor="email" className="form-label">Email</label>
3. All attributes are camelCase.
onclick becomes onClick. tabindex becomes tabIndex. aria-* and data-* attributes are the only exceptions and stay hyphenated.
<button onClick={handleClick} tabIndex={0}>Save</button>
4. Curly braces let you escape into JavaScript.
Anything inside { } is a JavaScript expression. Use it for values, computed strings, function calls, ternaries, and so on.
const name = "Ada";
<p>Hello, {name.toUpperCase()}!</p>
You cannot use statements inside JSX — no if, no for. Only expressions. We will see how to work around that below.
5. Every tag must close.
<br> is valid HTML. In JSX it must be <br />. The same applies to <img />, <input />, and <hr />.
Elements vs. components
These two words sound interchangeable. They are not.
- A component is the function you define.
- An element is what you get when React calls that function with JSX — a plain JavaScript object describing what should appear.
function Button() { return <button>Click</button>; }
const element = <Button />; // an element — a description
You rarely manipulate elements directly. The distinction matters because it explains why React can render the same component a thousand times cheaply — each rendered element is just a small object.
Composing components
The real power of components is composition. Any component can render any other component.
function Avatar() {
return <img src="/me.png" alt="avatar" />;
}
function Name() {
return <span>Ada Lovelace</span>;
}
function ProfileHeader() {
return (
<header>
<Avatar />
<Name />
</header>
);
}
ProfileHeader does not know how Avatar is implemented — it only knows it exists. Replace Avatar with a different version and ProfileHeader is unaffected. This is how large React apps stay tidy: small, focused components that combine into bigger ones.
The children prop
Components often need to wrap arbitrary content. React passes that content as a special prop called children.
function Card({ children }) {
return <div className="card">{children}</div>;
}
function Page() {
return (
<Card>
<h2>Heading</h2>
<p>Some body text.</p>
</Card>
);
}
Anything between the opening and closing tags of <Card> becomes its children. This is the standard pattern for layout components, modals, list wrappers — anywhere “this thing contains other things.”
We will cover other props in the next post. children is worth introducing now because every React tutorial uses it from day one.
Try it yourself. In the Vite project from the previous post, create a Card component in src/Card.jsx, export it, and render two <Card> elements from App.jsx with different children. Confirm both render independently.
Conditional rendering
JSX expressions cannot contain if statements directly, but they can contain any JavaScript expression. The two patterns you will use constantly are the ternary and the logical AND.
function Status({ online }) {
return (
<p>
{online ? "Online now" : "Offline"}
</p>
);
}
When you only want to render something if a condition is true — with no else branch — use &&:
function Inbox({ unread }) {
return (
<div>
<h2>Inbox</h2>
{unread > 0 && <p>You have {unread} unread messages.</p>}
</div>
);
}
One subtle warning. 0 && <p>...</p> evaluates to 0, and React will render the number zero in the page. Prefer an explicit boolean when the value could be a number:
{unread > 0 && <p>You have {unread} unread messages.</p>}
If a branch is large, lift the logic out of the JSX entirely:
function Greeting({ user }) {
let message;
if (!user) {
message = <p>Please sign in.</p>;
} else if (user.admin) {
message = <p>Welcome back, {user.name} (admin).</p>;
} else {
message = <p>Welcome back, {user.name}.</p>;
}
return <header>{message}</header>;
}
Plain if statements run normally — they are just outside the JSX.
Rendering lists
To render a list of items, map an array to JSX:
function FruitList() {
const fruits = ["apple", "banana", "cherry"];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}
Two things to notice.
The key prop
Every element in a list needs a unique, stable key. React uses it to identify which items changed when the list updates. A good key is something that stays the same across renders — a database id, a slug, a UUID. The array index works for static lists but breaks the moment items are reordered or removed:
// Fine for a list that never changes
fruits.map((fruit, i) => <li key={i}>{fruit}</li>)
// Better for any list that can change
items.map((item) => <li key={item.id}>{item.name}</li>)
If you forget key, React logs a warning in the console. Treat that warning as a real bug.
Wrap the map in { }
map returns an array, and JSX renders arrays just fine — but the call must sit inside { } because it is a JavaScript expression, not markup.
Try it yourself. Render a list of five users with name and id properties. Use id as the key. Then add a sixth user to the array, save, and confirm the new row appears without React warnings.
Component file conventions
A few conventions almost every React project follows. They are not enforced by the compiler, but adopting them makes your code recognisable to other developers.
- One component per file. Name the file after the component —
Card.jsx,ProfileHeader.jsx. - PascalCase for components, camelCase for files of helpers.
Button.jsxcontains a component;formatDate.jscontains a function. - Default export the main component. Named exports for siblings.
- Co-locate styles.
Card.jsxnext toCard.csskeeps things easy to find.
// src/Button.jsx
import './Button.css';
function Button({ children }) {
return <button className="btn">{children}</button>;
}
export default Button;
In a larger codebase you might place components under src/components/. There is no single right answer — pick a convention and apply it consistently.
A small worked example
Let’s combine everything into one component that lists a few articles, conditionally shows a “new” badge, and uses children for layout.
function Card({ children }) {
return <article className="card">{children}</article>;
}
function ArticleList() {
const articles = [
{ id: 1, title: "React Basics", isNew: true },
{ id: 2, title: "Hooks Explained", isNew: false },
{ id: 3, title: "Routing with React Router", isNew: true },
];
return (
<section>
<h1>Latest Articles</h1>
{articles.map((article) => (
<Card key={article.id}>
<h2>{article.title}</h2>
{article.isNew && <span className="badge">New</span>}
</Card>
))}
</section>
);
}
export default ArticleList;
Every concept in this post appears in those twenty lines: components, JSX, composition, children, conditional rendering, lists with keys.
Recap
You now know:
- A component is a PascalCase function that returns JSX
- JSX compiles to
React.createElementcalls — every rule follows from that - Return exactly one root element, or use a
<>fragment</> classNameinstead ofclass, camelCase event handlers, every tag must close- Components compose by being used inside other components
childrenlets a parent wrap arbitrary content- Conditional rendering uses ternaries and
&&; lists use.map()with a stablekey
Next steps
Components alone are static. The next post adds the two ideas that make them dynamic — props for passing data in and state for data that can change over time. These two concepts together cover most of what makes React feel like React.
→ Next: React Props and State for Beginners
Questions or feedback? Email codeloomdevv@gmail.com.