Components and JSX
What is JSX?
JSX (JavaScript XML) is a syntax extension for JavaScript that lets you write HTML-like markup directly inside JavaScript code. React introduced it, but JSX is not tied exclusively to React — any build tool can process it.
Instead of calling DOM APIs to manually create and attach elements, you write declarative markup that looks familiar. Behind the scenes, a build tool like Babel or ESBuild transforms your JSX into plain JavaScript that the browser can execute.
// JSX — looks like HTML, runs in no browser directly
const heading = <h1 className="title">Hello, world!</h1>;
JSX is not a string. It does not get inserted as innerHTML. It compiles to JavaScript function calls, making it safe by default.
JSX Expressions — Embedding {}
Any valid JavaScript expression can be embedded inside JSX using curly braces {}. This includes variables, calculations, function calls, and ternary operators.
const name = "Alice";
const year = new Date().getFullYear();
const Greeting = () => (
<div>
<h1>Hello, {name}!</h1>
<p>The year is {year}.</p>
<p>2 + 2 = {2 + 2}</p>
</div>
);
Attributes in JSX
JSX attributes work differently from HTML. All attribute names use camelCase, and values are JavaScript expressions:
// className instead of class
const Box = () => <div className="box">Content</div>;
// Boolean attributes require an explicit value
const Button = ({ disabled }) => (
<button disabled={disabled}>Press me</button>
);
// Dynamic attribute values
const DynamicImage = ({ src, alt }) => (
<img src={src} alt={alt} className="photo" />
);
How JSX Gets Compiled
JSX cannot run in a browser without transformation. A build tool like Babel, ESBuild, or SWC processes your .jsx files and converts every JSX element into a call to React.createElement().
// What you write:
const element = <h1 className="title">Hello</h1>;
// What Babel/ESBuild outputs:
const element = React.createElement("h1", { className: "title" }, "Hello");
React.createElement() produces a plain JavaScript object — a virtual DOM node. React reads this description and constructs the real DOM from it.
You can write createElement calls directly without JSX, though the result is harder to read:
const element = React.createElement(
"div",
{ className: "container" },
React.createElement("h1", null, "Welcome"),
React.createElement("p", null, "No JSX required, but less pleasant.")
);
Modern React (17+) ships with the new JSX transform, which means you no longer need to write import React from 'react' in every file just because you use JSX. The transform handles it automatically.
Components as Functions
In modern React, a component is simply a JavaScript function that returns JSX. These are called functional components:
const Heading = () => {
return <h1>Hello, React</h1>;
};
const App = () => {
return (
<div>
<Heading />
</div>
);
};
Component names must start with a capital letter. React uses this to distinguish your custom components (<Heading />) from built-in HTML elements (<div />, <span />). Without the capital, React treats <heading /> as a generic HTML tag.
A component can only return one root element. If you need to return multiple siblings without wrapping them in a div, use a fragment <>...</>:
const Meta = () => (
<>
<title>Page Title</title>
<meta name="description" content="A page" />
</>
);
Props — Passing Data to Components
Props are how you pass data into a component, functioning like arguments to a function. The parent component passes them and the child receives them as an object:
// Parent passes props
const App = () => {
return <Greet name="Charlie" score={85} />;
};
// Child receives props as its first argument
const Greet = (props) => {
return (
<p>
{props.name} scored {props.score}
</p>
);
};
Destructuring Props
Destructuring in the function signature is the cleanest way to extract props:
// Clean destructuring
const Greet = ({ name, score }) => (
<p>{name} scored {score}</p>
);
// Default values during destructuring
const Badge = ({ label, color = "blue" }) => (
<span style={{ backgroundColor: color }}>{label}</span>
);
// Collecting remaining props with spread
const Button = ({ label, ...rest }) => (
<button {...rest}>{label}</button>
);
Conditional Rendering
JSX has no if statements inside markup — instead you use JavaScript expressions.
Logical AND (&&)
Renders the right side only when the condition is true:
const Notification = ({ message }) => {
return message && <p className="alert">{message}</p>;
};
Watch out for falsy values. If message is 0 or "", React renders the number or empty string. For numbers that might be zero, convert explicitly: {messages.length > 0 && ...}.
Ternary Operator
Use ? : when you need an if/else branch:
const Status = ({ isOnline }) => (
<span>{isOnline ? "🟢 Online" : "⚫ Offline"}</span>
);
Early Returns with null
Return null from a component when it has nothing to render:
const PrivateMessage = ({ isAuthenticated, content }) => {
if (!isAuthenticated) return null;
return <p>{content}</p>;
};
This is cleaner than nesting ternary operators around the entire JSX tree.
Lists and Keys
Render arrays by mapping them to JSX elements. Always provide a key prop — React needs it to track which items changed efficiently:
const ColorList = ({ colors }) => (
<ul>
{colors.map((color) => (
<li key={color}>{color}</li>
))}
</ul>
);
Keys must be unique among siblings and stable across renders. Prefer real IDs over array indexes when items can be reordered or removed:
const users = [
{ id: 101, name: "Priya" },
{ id: 102, name: "Marcus" },
{ id: 103, name: "Yuki" },
];
const UserList = ({ users }) => (
<ul>
{users.map(({ id, name }) => (
<li key={id}>{name}</li>
))}
</ul>
);
If your data has no stable ID, the index is acceptable as a last resort — but be aware it will cause reconciliation bugs when the list order changes.
Component Composition
Composition is the practice of building complex UIs from small, single-responsibility components. A well-designed component does one thing well and accepts children to let parents inject content:
const Card = ({ children }) => (
<div className="card">{children}</div>
);
const Article = () => (
<Card>
<h2>Article Title</h2>
<p>Article body text goes here.</p>
</Card>
);
Composing with named props also works well for more structured layouts:
const Avatar = ({ src, alt }) => (
<img src={src} alt={alt} className="avatar" />
);
const Profile = ({ name, avatarUrl, bio }) => (
<div className="profile">
<Avatar src={avatarUrl} alt={name} />
<div>
<h3>{name}</h3>
<p>{bio}</p>
</div>
</div>
);
Styling in JSX
Two key differences from HTML: class is reserved in JavaScript, so use className. And all CSS property names use camelCase.
Inline Styles
Pass a JavaScript object to the style attribute:
const StyledBox = () => (
<div style={{
backgroundColor: "#f5f5f5",
padding: "20px",
borderRadius: "8px"
}}>
Inline styles
</div>
);
CSS Modules
CSS Modules scope class names to the component, preventing accidental collisions across your app:
// Button.module.css
// .btn { padding: 8px 16px; }
// .primary { background: #3b82f6; }
import styles from "./Button.module.css";
const Button = ({ variant = "primary", label }) => {
const cls = variant === "secondary" ? styles.secondary : styles.primary;
return <button className={`${styles.btn} ${cls}`}>{label}</button>;
};
JSX vs HTML — Key Differences
| Feature | HTML | JSX |
|---|---|---|
| Class attribute | class="..." | className="..." |
Label for attribute | for="..." | htmlFor="..." |
| Event handlers | onclick="..." | onClick={...} |
| Self-closing tags | <br>, <img> | Must use <br />, <img /> |
| Boolean attributes | Present or absent | Must set value: disabled={true} |
| Root element | Multiple siblings allowed | Single root element (or use fragments) |
| Dynamic values | String concatenation | Embed expressions with {} |
JSX also enforces that all attribute names use camelCase — onclick is wrong, onClick is correct. Misspelling an attribute name produces no error at compile time, but the event will simply never fire.
JSX and Security
One of JSX’s strongest features is automatic escaping. Any value embedded in {} is converted to a string and escaped before being inserted into the DOM. This makes XSS attacks through user input much harder:
const Comment = ({ text }) => <p>{text}</p>;
// text = '<script>stealCookies()</script>'
// React renders it as harmless escaped text, not executable HTML
If you truly need to render raw HTML (and you almost never should), React exposes dangerouslySetInnerHTML. Using it bypasses the escaping layer entirely — treat it as a last resort with untrusted data:
const RawHTML = ({ html }) => (
<div dangerouslySetInnerHTML={{ __html: html }} />
);
See Also
- React: Introduction and Setup — Get React running in your project from scratch
- JavaScript Classes — Understand the class syntax that older React components rely on