On this page

JSX and Components: Building Blocks of React

15 min read TextCh. 1 — React Fundamentals

What is JSX?

JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like markup directly inside your JavaScript and TypeScript code. It is React's primary way of describing the UI.

// This is JSX
const element = <h1 className="title">Hello, React!</h1>;

// After compilation by Babel/TypeScript, it becomes:
const element = React.createElement('h1', { className: 'title' }, 'Hello, React!');

JSX is purely syntactic sugar — your browser never sees JSX. Your build tool (Vite, TypeScript compiler) transforms it into standard React.createElement() calls. With React 17+ and the new JSX transform, you no longer need to import React manually in every file.

JSX Rules

There are a few rules that distinguish JSX from plain HTML:

1. One Root Element

Every JSX expression must return a single root element. If you need multiple sibling elements, wrap them in a <div> or, better, use a Fragment:

// Wrong — two siblings at the top level
function BadComponent() {
  return (
    <h1>Title</h1>
    <p>Paragraph</p>
  );
}

// Correct — using a Fragment
function GoodComponent() {
  return (
    <>
      <h1>Title</h1>
      <p>Paragraph</p>
    </>
  );
}

2. Close All Tags

Every tag must be explicitly closed. Self-closing tags that are valid in HTML must also be self-closed in JSX:

// HTML: <br>, <img>, <input>
// JSX:  <br />, <img />, <input />
function Form() {
  return (
    <form>
      <label htmlFor="email">Email</label>
      <input type="email" id="email" />
      <br />
      <button type="submit">Send</button>
    </form>
  );
}

3. camelCase Attributes

JSX attributes follow JavaScript naming conventions:

HTML JSX
class className
for htmlFor
tabindex tabIndex
onclick onClick
onchange onChange
stroke-width strokeWidth

4. JavaScript Expressions in Curly Braces

Use {} to embed any JavaScript expression inside JSX:

const user = { name: 'Alice', score: 1500 };
const isAdmin = true;

function UserBadge() {
  return (
    <div>
      {/* String interpolation */}
      <p>Welcome, {user.name}!</p>

      {/* Math expression */}
      <p>Score: {user.score * 2} (double XP)</p>

      {/* Conditional with ternary */}
      <span>{isAdmin ? 'Admin' : 'User'}</span>

      {/* Function call */}
      <p>Joined: {new Date().getFullYear()}</p>
    </div>
  );
}

Functional Components

In modern React (v16.8+), functional components are the standard way to write components. A functional component is just a function that:

  1. Accepts a single props object as its argument.
  2. Returns JSX (or null to render nothing).
// The simplest possible component
function Hello() {
  return <p>Hello, world!</p>;
}

// With TypeScript props
interface GreetingProps {
  name: string;
  role?: string; // optional
}

function Greeting({ name, role = 'User' }: GreetingProps) {
  return (
    <div>
      <h2>{name}</h2>
      <small>{role}</small>
    </div>
  );
}

Component Composition

The real power of React comes from composing small components into larger ones. This is how you build complex UIs from simple parts:

function Avatar({ src, alt }: { src: string; alt: string }) {
  return <img src={src} alt={alt} className="avatar" width={40} height={40} />;
}

function UserInfo({ name, email }: { name: string; email: string }) {
  return (
    <div className="user-info">
      <strong>{name}</strong>
      <small>{email}</small>
    </div>
  );
}

function UserHeader({ user }: { user: { name: string; email: string; avatar: string } }) {
  return (
    <header className="user-header">
      <Avatar src={user.avatar} alt={`${user.name}'s avatar`} />
      <UserInfo name={user.name} email={user.email} />
    </header>
  );
}

Each component is responsible for one thing. Avatar renders an image, UserInfo renders text, UserHeader composes both.

Conditional Rendering

React has no special directive for conditionals — you use plain JavaScript:

interface MessageProps {
  status: 'loading' | 'success' | 'error';
  text?: string;
}

function Message({ status, text }: MessageProps) {
  // Early return
  if (status === 'loading') {
    return <p>Loading...</p>;
  }

  return (
    <div>
      {/* Short-circuit evaluation */}
      {status === 'error' && <p className="error">Something went wrong.</p>}

      {/* Ternary operator */}
      {status === 'success' ? (
        <p className="success">{text ?? 'Done!'}</p>
      ) : null}
    </div>
  );
}

Rendering Lists

Use Array.map() to render lists of elements. Always provide a key prop:

interface Task {
  id: string;
  title: string;
  done: boolean;
}

const tasks: Task[] = [
  { id: 'a1', title: 'Learn JSX', done: true },
  { id: 'b2', title: 'Build a component', done: false },
  { id: 'c3', title: 'Ship to production', done: false },
];

function TaskList() {
  return (
    <ul>
      {tasks.map((task) => (
        <li
          key={task.id}
          style={{ textDecoration: task.done ? 'line-through' : 'none' }}
        >
          {task.title}
        </li>
      ))}
    </ul>
  );
}

Filtering and Transforming Lists

You can chain array methods before rendering:

function DoneTaskList() {
  const doneTasks = tasks.filter((t) => t.done);

  return (
    <section>
      <h3>Completed ({doneTasks.length})</h3>
      <ul>
        {doneTasks.map((task) => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
    </section>
  );
}

Inline Styles

For dynamic styles in JSX, pass a JavaScript object to the style prop. Property names use camelCase:

function ProgressBar({ value }: { value: number }) {
  return (
    <div className="progress-track">
      <div
        className="progress-fill"
        style={{
          width: `${value}%`,
          backgroundColor: value >= 100 ? '#22c55e' : '#3b82f6',
          transition: 'width 0.3s ease',
        }}
        role="progressbar"
        aria-valuenow={value}
        aria-valuemin={0}
        aria-valuemax={100}
      />
    </div>
  );
}

Naming Conventions

React components must start with a capital letter. This is how React distinguishes between custom components and native HTML elements:

// React sees <div> as the HTML element
const a = <div />;

// React sees <Div> as a component — it will look for a Div function
const b = <Div />; // TS error if Div is not defined

Keep component files named after the component: ProductCard.tsxfunction ProductCard.

Exporting Components

You can use named or default exports. Prefer named exports for better refactoring support:

// Named export (preferred in most codebases)
export function Button({ label }: { label: string }) {
  return <button type="button">{label}</button>;
}

// Default export (common for page-level components)
export default function HomePage() {
  return <main><h1>Home</h1></main>;
}

In the next lesson, you will explore props and children in depth, including how to build flexible, composable components with TypeScript.

Always add the key prop when rendering lists
React uses the `key` prop to efficiently identify which items changed, were added, or were removed. Keys must be unique among siblings. Use stable IDs from your data — never use the array index as a key unless the list is truly static and never reordered.
JSX is not HTML
JSX looks like HTML but it compiles to JavaScript. Differences include: use `className` instead of `class`, `htmlFor` instead of `for`, and all tags must be self-closed when they have no children (e.g., `<br />`, `<img />`). CSS properties in JSX use camelCase: `backgroundColor` instead of `background-color`.
Fragments avoid unnecessary DOM nodes
When a component needs to return multiple elements without a wrapper div, use a Fragment: `<>...</>` or `<React.Fragment>...</React.Fragment>`. Fragments render nothing in the DOM, keeping your HTML clean.
interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
  tags: string[];
}

interface ProductCardProps {
  product: Product;
  onAddToCart: (id: number) => void;
}

function ProductCard({ product, onAddToCart }: ProductCardProps) {
  return (
    <article className="product-card">
      <h2>{product.name}</h2>
      <p className="price">${product.price.toFixed(2)}</p>

      {/* Conditional rendering */}
      {product.inStock ? (
        <span className="badge badge--green">In stock</span>
      ) : (
        <span className="badge badge--red">Out of stock</span>
      )}

      {/* List rendering */}
      <ul className="tags">
        {product.tags.map((tag) => (
          <li key={tag}>{tag}</li>
        ))}
      </ul>

      <button
        type="button"
        disabled={!product.inStock}
        onClick={() => onAddToCart(product.id)}
      >
        Add to cart
      </button>
    </article>
  );
}

export default ProductCard;