Doing React the Right Way - 3/10
JSX, Components, and Props: The React Trinity
You have a professional development environment running. You’ve seen the Vite dev server start instantly and watched changes appear in real-time. Now it’s time to understand React’s core concepts—the three pillars that make everything else possible.
These aren’t optional concepts or advanced patterns. They’re the fundamental building blocks of every React application ever built. Master these, and you’ll have the foundation for everything else. Skip understanding them deeply, and every React codebase will feel like a mystery.
Today we’re covering Components, JSX, and Props—React’s holy trinity.
Components: Functions That Return UI
Let’s demystify React’s most fundamental concept. A React component is simpler than you might expect: it’s just a JavaScript function that returns a description of what the UI should look like.
Create a new file src/Greeting.tsx
:
// src/Greeting.tsx
function Greeting() {
return <h1>Hello, React developer!</h1>;
}
export default Greeting;
That’s it. You’ve created a React component. It’s a function named Greeting
that returns something that looks like HTML (we’ll explain that shortly).
Now use it in your App.tsx
:
// src/App.tsx
import Greeting from "./Greeting";
function App() {
return (
<div>
<Greeting />
<Greeting />
<Greeting />
</div>
);
}
export default App;
Check your browser—you’ll see your greeting displayed three times. You’ve just experienced the power of component-based architecture: build a reusable piece of UI and compose it into larger interfaces.
The PascalCase Rule
Notice that our component function is named Greeting
, not greeting
. This isn’t a style preference—it’s a requirement. React’s compiler distinguishes between your custom components and native HTML elements based on the first letter:
<Greeting />
(capital letter) → React calls yourGreeting
function<div />
(lowercase) → React creates a standard HTML div element
If you name your component greeting
, React will try to render an HTML tag called <greeting>
, which doesn’t exist. Your component won’t render, and you’ll get confusing error messages.
This convention—PascalCase for component names—is non-negotiable in React.
JSX: The Syntax That Changed Everything
That HTML-looking syntax in your JavaScript might feel uncomfortable at first. It’s called JSX (JavaScript XML), or TSX when using TypeScript. Let’s address the elephant in the room.
Breaking Old Rules for Good Reasons
Traditional web development taught separation of concerns: HTML, CSS, and JavaScript should live in separate files. JSX deliberately breaks this rule, and for good reason.
In modern web applications, your UI markup and UI logic are intrinsically linked. A component’s job is to render interface elements—its markup is its logic. Separating them artificially makes code harder to understand and maintain.
JSX embraces this reality by letting you write markup directly in your component logic.
JSX Is Not HTML
Here’s the crucial insight: JSX is not HTML. It’s syntactic sugar for JavaScript function calls.
When you write:
<h1 className="title">Welcome</h1>
Your build tool (Vite) transforms it into:
React.createElement("h1", { className: "title" }, "Welcome");
JSX is just a more readable way to write React.createElement()
calls. Every JSX element becomes a JavaScript object describing that piece of UI. React takes this tree of objects (the Virtual DOM) and figures out how to render it efficiently.
JSX Rules and Patterns
Because JSX is JavaScript, not HTML, it has its own rules:
1. className Instead of class
Since class
is a reserved word in JavaScript, you use className
for CSS classes:
// Correct
<div className="container">Content</div>
// Wrong - will cause errors
<div class="container">Content</div>
Similarly, for
becomes htmlFor
on label elements.
2. JavaScript Expressions in Curly Braces
Use curly braces {}
to embed JavaScript expressions in your JSX:
function UserProfile() {
const user = {
name: "Sarah Chen",
age: 28,
profession: "Software Engineer",
};
const getGreeting = () => `Nice to meet you, ${user.name}!`;
return (
<div>
<h2>{user.name.toUpperCase()}</h2>
<p>Age: {user.age}</p>
<p>Next year: {user.age + 1}</p>
<p>Profession: {user.profession}</p>
<p>{getGreeting()}</p>
</div>
);
}
You can use any JavaScript expression inside curly braces. You cannot use statements like if/else
, for
, or switch
. For conditional rendering, use ternary operators or logical AND (&&
).
3. All Tags Must Be Closed
HTML allows unclosed tags like <br>
or <img>
. JSX requires every tag to be closed:
// Correct
<img src="photo.jpg" alt="Profile" />
<br />
<input type="text" />
// Wrong - syntax errors
<img src="photo.jpg" alt="Profile">
<br>
<input type="text">
4. Single Root Element Requirement
A component must return a single root element. This is a fundamental JSX constraint:
// Wrong - multiple root elements
function BadComponent() {
return (
<h1>Title</h1>
<p>Description</p>
);
}
// Correct - single root element
function GoodComponent() {
return (
<div>
<h1>Title</h1>
<p>Description</p>
</div>
);
}
Sometimes you don’t want to add an extra div just for JSX requirements. React provides Fragments for this:
import React from "react";
// Using React.Fragment
function ComponentWithFragment() {
return (
<React.Fragment>
<h1>Title</h1>
<p>Description</p>
</React.Fragment>
);
}
// Using shorthand syntax (more common)
function ComponentWithShorthand() {
return (
<>
<h1>Title</h1>
<p>Description</p>
</>
);
}
Fragments group elements without adding extra DOM nodes. Use them when you need multiple root elements without wrapper divs.
Props: Passing Data Between Components
Components become truly powerful when they can accept data and customize their output accordingly. This is where props (properties) come in.
Props are to React components what arguments are to JavaScript functions. They’re how parent components pass data down to child components.
Basic Props Usage
Let’s make our Greeting component more flexible:
// src/Greeting.tsx
// 1. Define the shape of props with TypeScript interface
interface GreetingProps {
name: string;
messageCount: number;
isVIP?: boolean; // Optional prop
}
// 2. Component function receives props as first argument
function Greeting(props: GreetingProps) {
return (
<div>
<h1>Hello, {props.name}!</h1>
<p>You have {props.messageCount} unread messages.</p>
{props.isVIP && <p>Thanks for being a VIP member!</p>}
</div>
);
}
export default Greeting;
Now use it in your parent component:
// src/App.tsx
import Greeting from "./Greeting";
function App() {
return (
<div>
<Greeting name="Alice" messageCount={5} isVIP={true} />
<Greeting name="Bob" messageCount={0} />
<Greeting name="Carol" messageCount={12} isVIP={false} />
</div>
);
}
export default App;
Notice how props are passed as attributes in JSX:
- String values:
name="Alice"
(in quotes) - Numbers, booleans, objects:
messageCount={5}
,isVIP={true}
(in curly braces)
Destructuring Props (The Cleaner Approach)
Instead of accessing props.name
repeatedly, destructure props in the function signature:
// src/Greeting.tsx (improved version)
interface GreetingProps {
name: string;
messageCount: number;
isVIP?: boolean;
}
// Destructure props directly in the function parameters
function Greeting({ name, messageCount, isVIP = false }: GreetingProps) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You have {messageCount} unread messages.</p>
{isVIP && <p>Thanks for being a VIP member!</p>}
</div>
);
}
export default Greeting;
This approach is cleaner and provides default values for optional props.
The Immutability Rule
This is crucial: components must never modify their own props. Props are owned by the parent component. The child component receives them as read-only data.
// Never do this
function BadComponent({ name }: { name: string }) {
name = "Modified!"; // ERROR - violates React's rules
return <h1>{name}</h1>;
}
This restriction ensures predictability. When you look at a component, you know that any data it displays came from its parent, not from internal mutations. This makes debugging tractable and data flow understandable.
Conditional Rendering Patterns
Props enable powerful conditional rendering patterns:
interface AlertProps {
type: "success" | "warning" | "error";
message: string;
showIcon?: boolean;
}
function Alert({ type, message, showIcon = true }: AlertProps) {
// Conditional styling based on props
const alertClass = `alert alert-${type}`;
// Conditional icon selection
const getIcon = () => {
if (!showIcon) return null;
switch (type) {
case "success":
return "✅";
case "warning":
return "⚠️";
case "error":
return "❌";
default:
return "";
}
};
return (
<div className={alertClass}>
{showIcon && <span className="alert-icon">{getIcon()}</span>}
<span className="alert-message">{message}</span>
</div>
);
}
// Usage
function App() {
return (
<div>
<Alert type="success" message="Data saved successfully!" />
<Alert
type="warning"
message="Please verify your email"
showIcon={false}
/>
<Alert type="error" message="Failed to connect to server" />
</div>
);
}
Understanding main.tsx Revisited
Now that you understand JSX and components, let’s revisit your application’s entry point with new clarity:
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
ReactDOM.createRoot(...)
: Finds the<div id="root"></div>
in your HTML and creates a React root for modern concurrent features.render(...)
: Tells React to render the provided JSX into the root element<App />
: Not an HTML tag—this tells React to call yourApp
function and render whatever it returns<React.StrictMode>
: A development helper that enables additional checks and warnings. It renders nothing visible but helps catch potential issues
The Foundation Is Set
You now understand React’s core building blocks:
Components: Functions that return UI descriptions, enabling modular and reusable interface pieces
JSX: A JavaScript syntax extension that makes writing UI descriptions intuitive while compiling to efficient function calls
Props: The mechanism for passing data from parent to child components, enabling customizable and flexible components
These concepts work together to create React’s declarative programming model. Instead of writing step-by-step instructions for DOM manipulation, you describe what your UI should look like based on current data. React handles the complex work of keeping the actual DOM synchronized with your descriptions.
In our next article, we’ll add memory to your components with state management, handle side effects with useEffect, and explore React’s modern concurrent features. But the foundation you’ve built today—understanding components, JSX, and props—will support everything else you learn.
You’re not just learning React syntax. You’re learning a new way to think about user interface development that emphasizes predictability, reusability, and maintainability.
That mindset shift is more valuable than any specific feature or API. It’s what transforms you from someone who uses React into someone who thinks in React.