Week 1: React Fundamentals, JSX & the Component Model

Every pattern later in this course — hooks in Week 2, routing in Week 6, server state in Week 8 — is built on top of one idea: a component is a function that takes data in and returns UI out. This week makes that idea concrete: what JSX actually compiles to, how React decides what to re-render, and the props-vs-state distinction that trips up almost everyone the first time they meet it.

Module 1 of 13 Week 1 of 20 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Explain what JSX compiles to and why React re-renders when it does
  • Build components that split cleanly along props vs. state
  • Set up and navigate a Vite + React + TypeScript project

1. Project Setup with Vite

This course uses Vite to scaffold and run React apps — it's faster than older tooling like Create React App (now discontinued) and is what most new React projects reach for today. One command gets you a working TypeScript project:

terminal
npm create vite@latest week-01 -- --template react-ts
cd week-01
npm install
npm run dev

npm run dev starts a local dev server (usually at http://localhost:5173) with hot module replacement — save a file and the browser updates instantly, without a full page reload or losing component state.

project layout
week-01/
├── index.html
├── package.json
├── vite.config.ts
└── src/
    ├── main.tsx      ← mounts the app into index.html
    ├── App.tsx        ← root component
    └── App.css
src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

createRoot(...).render(<App />) is the one line where React takes over a DOM node and starts managing everything inside it. Everything else in this course happens inside that tree.

2. JSX & Elements

JSX — the HTML-like syntax inside your .tsx files — isn't valid JavaScript on its own. A build tool (Vite, via esbuild) compiles it into plain function calls before your code ever runs in the browser.

what you write
const heading = <h1 className="title">Hello, React</h1>;
what it compiles to
const heading = jsx('h1', { className: 'title', children: 'Hello, React' });

That function call returns a plain JavaScript object — a React element — describing what should appear on screen: a type, some props, and children. React elements are cheap, immutable descriptions, not real DOM nodes yet.

expressions in JSX
function Greeting({ name }: { name: string }) {
  const hour = new Date().getHours();
  const greeting = hour < 12 ? 'Good morning' : 'Good afternoon';

  return (
    <div>
      <p>{greeting}, {name}!</p>
      {name === 'Ada' && <span>⭐ Welcome back</span>}
    </div>
  );
}

Curly braces {'{ }'} drop back into JavaScript inside JSX — any expression works, including ternaries for either/or rendering and && for conditionally rendering something or nothing.

Why className, not class?

JSX attributes are JavaScript object properties under the hood, and class is a reserved word in JavaScript. This is also why event handlers are onClick (camelCase), not onclick — JSX props follow JS naming conventions, not HTML's.

3. The Virtual DOM: Why React Re-Renders

Every time a component's state or props change, React calls that component's function again, producing a new tree of elements. Instead of touching the real DOM immediately, React diffs the new element tree against the previous one, and only applies the minimal set of real DOM changes needed.

Counter.tsx
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  console.log('Counter rendered'); // logs on every click

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

Clicking the button calls setCount, which schedules a re-render: Counter() runs again from the top, producing a new element tree. React compares it to the previous tree, sees only the text node changed, and updates just that text node in the real DOM — not the whole button, and definitely not the whole page.

This is the core mental model you'll build on for the rest of the course: a render is not the same thing as a DOM update. "Rendering" means React calling your component function to figure out what should be on screen; the actual DOM only changes where the result differs from last time.

4. Props vs. State — and One-Way Data Flow

This is the distinction that causes the most confusion in your first week with React, so it's worth being precise: props are data passed into a component by its parent — read-only from the component's own point of view. State is data a component owns and can change itself, via useState (which you'll go deep on in Week 2).

props-vs-state.tsx
interface UserCardProps {
  name: string;   // prop -- the parent decides this, UserCard can't change it
}

function UserCard({ name }: UserCardProps) {
  const [expanded, setExpanded] = useState(false); // state -- UserCard owns this

  return (
    <div>
      <p>{name}</p>
      <button onClick={() => setExpanded(!expanded)}>
        {expanded ? 'Show less' : 'Show more'}
      </button>
      {expanded && <p>Extra details about {name}...</p>}
    </div>
  );
}

// The parent controls the prop; UserCard controls its own state
<UserCard name="Ada Lovelace" />

Data flows one way: from parent to child, via props. A child can never directly hand a new value back up into its parent's state — if a child needs to change something the parent owns, the parent passes down a function as a prop, and the child calls it:

lifting state up
function Parent() {
  const [selected, setSelected] = useState<string | null>(null);

  return <OptionList selected={selected} onSelect={setSelected} />;
}

function OptionList({ selected, onSelect }: {
  selected: string | null;
  onSelect: (value: string) => void;
}) {
  return (
    <button onClick={() => onSelect('blue')}>
      {selected === 'blue' ? '✓ ' : ''}Blue
    </button>
  );
}

This pattern — called lifting state up — is how two sibling components end up "sharing" state: the state actually lives in their closest common parent, and both children receive it (and a way to change it) via props.

Why this matters later

Lifting state up works fine for a few components. Once you're lifting the same piece of state through five layers just to pass it down, that's the exact pain point Context (Week 10) and global state libraries solve.

5. Composing Components

A React app is a tree of components rendering other components. The children prop is what lets a component wrap arbitrary content it doesn't need to know the shape of — the same pattern behind every layout, card, and modal you'll build:

Card.tsx
interface CardProps {
  title: string;
  children: React.ReactNode;
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card__body">{children}</div>
    </div>
  );
}

// Usage -- Card has no idea what's inside it, and doesn't need to
<Card title="Profile">
  <UserCard name="Grace Hopper" />
  <button>Edit</button>
</Card>

Notice Card is completely reusable — it knows how to render a titled box, and nothing about what goes inside one. This "wrap arbitrary children" shape reappears constantly: layout components, modals, list wrappers, and — in Week 11 — the compound-component pattern built entirely on top of it.

6. Hands-on Exercise

Hands-on

Build a filterable profile list from scratch

Practice the props/state/composition split by building a small, real UI — no external libraries, just what this week covered.

starter data
const people = [
  { id: 1, name: 'Ada Lovelace', role: 'Mathematician' },
  { id: 2, name: 'Grace Hopper', role: 'Computer Scientist' },
  { id: 3, name: 'Alan Turing', role: 'Mathematician' },
  { id: 4, name: 'Katherine Johnson', role: 'Physicist' },
];

Requirements:

  1. Scaffold a new Vite + React + TypeScript project and get it running with npm run dev.
  2. Build a PersonCard component that takes a person as props and renders their name and role.
  3. Build a PersonList component that owns a filterText piece of state (via useState) and a text input bound to it, rendering one PersonCard per person whose name matches the filter (case-insensitive).
  4. Wrap the whole list in a Card component (from Section 5) that takes a title prop and renders PersonList as its children.
  5. Add a "no results" message, rendered conditionally, when the filter matches nobody.
Hint

The filter state belongs in PersonList, not PersonCardPersonCard should stay a "dumb" component that only receives props. If you find yourself wanting state inside PersonCard for this exercise, that's a sign the state lives one level too low.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What does a line of JSX like <h1>Hi</h1> actually become before it reaches the browser?

The build tool compiles it into a plain function call (like jsx('h1', { children: 'Hi' })) that returns a JavaScript object describing the element — type, props, and children. It's a description of what should be on screen, not a real DOM node.

Q2

When state changes and a component "re-renders," does React necessarily touch the real DOM?

Not necessarily, and even when it does, not everywhere. "Re-rendering" means React calls the component function again and gets a new element tree, which it diffs against the previous tree. Only the parts of the real DOM that actually differ get updated — a render and a DOM update are two different things.

Q3

Why can't a child component directly modify a piece of state that lives in its parent?

React data flow is one-directional: props only travel from parent to child, never back up automatically. A child can only affect a parent's state if the parent explicitly passes down a function as a prop (like onSelect) for the child to call — the parent still owns and controls the actual state update.

Q4

What makes the children prop different from a regular prop like title?

Nothing structurally — it's still just a prop. The difference is convention and syntax: whatever you nest between a component's opening and closing JSX tags gets passed automatically as its children prop, which lets a wrapper component (like Card) render arbitrary, unknown content without needing a dedicated prop for every possible thing you might put inside it.