Skip to content

React Components, State, and Navigation

Step 19 of 45

Components, JSX, and Project Structure

Introduction

This lesson opens chapter 5, aligned with course 5 of the Meta Front-End Developer pathway ("React Basics"). Until now you built pages from HTML, CSS and JavaScript that manipulates the DOM directly. That works for small pages, but as an interface grows, keeping the DOM in sync with your data by hand becomes slow and error-prone. React is a JavaScript library that lets you describe what the interface should look like for a given piece of data, and then updates the DOM for you.

React's unit of construction is the component: a reusable function that returns a piece of the interface. In this lesson you learn to write function components, use JSX (the syntax that looks like HTML inside JavaScript), pass data with props, nest components with children, and set up and organize a small standalone React project. The practice scenario is original: a catalog of Harbor Workshops course cards built from one reusable WorkshopCard component.

Learning objectives

  1. Explain what React does and why components help.
  2. Set up a small React project with a current build tool and explain the generated structure.
  3. Write function components with capitalized names and export and import them.
  4. Write valid JSX: one root, expressions in braces, className, self-closing tags, and camelCase attributes.
  5. Pass data with props, use destructuring and default values, and explain one-way data flow.
  6. Use the children prop for composition.
  7. Explain why props are read-only.
  8. Organize files and render a list of components from data.

1. What React is, and what it is not

React is a library for building user interfaces from components. Its core idea is declarative rendering: you write a function that, given data, returns what the screen should show. When the data changes, React calls the function again and updates only the parts of the real DOM that differ.

Compare two ways to show a seat count.

Imperative (manual DOM updates):

const el = document.querySelector('#seats');
el.textContent = 'Seats left: ' + seats;
// and remember to call this every time seats changes, everywhere

Declarative (React):

function SeatCounter({ seats }) {
  return <p>Seats left: {seats}</p>;
}

You never write "update the paragraph." You describe the paragraph for any value of seats.

React is only the view layer. It does not include a router, a data-fetching client, or a form library; you add those as needed (routing arrives in lesson 4 of this chapter). Learn the principles; the surrounding tools change faster than React's core ideas.

2. Setting up a project

The React team no longer recommends Create React App for new projects: it is deprecated. For learning, the official guidance for building from scratch is to start with a build tool such as Vite, and for production applications the documentation recommends starting with a framework. In this course you build small standalone apps, so Vite is a good fit.

Prerequisites: Node.js (a current LTS version) and a terminal (chapter 3).

npm create vite@latest harbor-workshops -- --template react
cd harbor-workshops
npm install
npm run dev

The dev server prints a local address such as http://localhost:5173. Edits you save appear in the browser immediately (hot reload). Template names and prompts change between Vite versions, so read the terminal output and the official guide if something differs.

The generated structure

harbor-workshops/
  index.html          # the single HTML page with <div id="root">
  package.json        # dependencies and scripts (dev, build, preview, lint)
  vite.config.js      # build tool configuration
  public/             # files copied as-is (favicon, robots.txt)
  src/
    main.jsx          # entry point: renders <App /> into #root
    App.jsx           # the top-level component
    App.css
    assets/           # images imported by components

The entry point connects React to the page:

// src/main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

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

createRoot takes a DOM node and renders the component tree inside it. StrictMode is a development helper: it runs extra checks and may call your components twice to reveal impure code. It does nothing in the production build.

The package.json scripts: npm run dev starts the dev server, npm run build creates optimized static files in dist/, and npm run preview serves that build locally for testing. Commit package.json and the lock file, but add node_modules and dist to .gitignore (chapter 3).

3. Function components

A component is a JavaScript function that returns JSX (or null). Its name must start with a capital letter; lowercase names are treated as HTML tags.

function Greeting() {
  return <h2>Welcome to Harbor Workshops</h2>;
}

export default function App() {
  return (
    <main>
      <Greeting />
      <Greeting />
    </main>
  );
}

Using <Greeting /> renders the function's result. Each use is an independent instance. Rules:

  • Define components at the top level of a module. Never define a component inside another component, because it would be a new component on every render and lose its state.
  • Keep one component per responsibility, and split when a component gets long or when a section is reused.
  • A component should be pure: given the same props, it returns the same JSX and does not change things that existed before it was called (no modifying outside variables during rendering).

Exports and imports

// WorkshopCard.jsx
export default function WorkshopCard() { /* ... */ }   // default export
export function Badge() { /* ... */ }                  // named export
import WorkshopCard, { Badge } from './WorkshopCard.jsx';

A file can have one default export (imported under any name) and many named exports (imported with braces, by exact name). Many teams use one component per file, named like the file, with a default export. Relative paths start with ./ or ../; some tools allow omitting the extension but being explicit avoids confusion.

4. JSX

JSX is a syntax extension that lets you write markup inside JavaScript. A build step compiles it to plain JavaScript function calls. It looks like HTML but has stricter rules.

Rules

  1. Return a single root element. Wrap siblings in a parent or a fragment <>...</> (a fragment adds no DOM node).
return (
  <>
    <h2>Pottery</h2>
    <p>Saturday morning</p>
  </>
);
  1. Close every tag. <img />, <input />, <br />.
  2. Use camelCase for most attributes, and className instead of class, htmlFor instead of for: <label htmlFor="email" className="field">.
  3. Put JavaScript expressions in curly braces.
const title = 'Pottery for Beginners';
const price = 45;
return (
  <article>
    <h3>{title}</h3>
    <p>Price: {price} EUR (VAT {(price * 0.2).toFixed(2)} EUR)</p>
    <img src={imageUrl} alt={`Photo for ${title}`} width="640" height="400" />
  </article>
);

Braces accept expressions (variables, function calls, ternaries, template strings), not statements (if, for). Strings in attributes can use quotes (alt="Logo"); anything dynamic uses braces (alt={title}).

  1. Inline styles take an object: <p style={{ color: 'crimson', fontSize: '1.25rem' }}>. The double braces are a JavaScript object inside the JSX expression. Prefer CSS classes for anything non-trivial.
  2. Comments inside JSX are {/* like this */}.

Escaping and safety

JSX escapes values placed in braces, so inserting user text such as <script> shows it as text and does not execute it. This is a protection against cross-site scripting. The prop dangerouslySetInnerHTML bypasses it and should be avoided unless the HTML is trusted and sanitized.

What renders, and what does not

true, false, null, and undefined render nothing. Numbers and strings render as text. Objects are not valid children and throw an error ("Objects are not valid as a React child"). Remember that 0 does render, which surprises people with expressions like {count && <List />} (covered in lesson 3).

5. Props and one-way data flow

Props (properties) are the inputs to a component, passed as attributes and received as a single object argument.

function WorkshopCard(props) {
  return (
    <article className="card">
      <h3>{props.title}</h3>
      <p>{props.date}</p>
    </article>
  );
}

<WorkshopCard title="Pottery for Beginners" date="Saturday, 7 November" />

Destructuring in the parameter list is the common idiom, with default values:

function WorkshopCard({ title, date, seatsLeft = 0, featured = false }) {
  return (
    <article className={featured ? 'card card--featured' : 'card'}>
      <h3>{title}</h3>
      <p>{date}</p>
      <p>{seatsLeft > 0 ? `${seatsLeft} seats left` : 'Sold out'}</p>
    </article>
  );
}

Passing non-string values requires braces: seatsLeft={12}, featured={true} (or just featured), tags={['clay', 'beginner']}, onBook={handleBook}.

Props are read-only

A component must never modify its props:

function Bad({ seatsLeft }) {
  seatsLeft = seatsLeft - 1;   // wrong: do not mutate props
}

If something must change over time, it belongs to state (next lesson). If a child needs to cause a change, the parent passes a function as a prop, and the child calls it.

One-way data flow

Data flows down the component tree, from parent to child, through props. A child cannot reach up and change the parent's variables. This makes behavior predictable: to find where a value comes from, follow props upward. When two components need the same data, the data lives in their closest common parent and flows down to both ("lifting state up", lesson 2).

Spreading and passing many props

const workshop = { title: 'Pottery', date: 'Saturday', seatsLeft: 4 };
<WorkshopCard {...workshop} />

Spreading is convenient but hides what a component receives. Use it with care; explicit props are easier to read.

6. Children and composition

Whatever you put between a component's tags arrives as the special prop children.

function Panel({ title, children }) {
  return (
    <section className="panel">
      <h2>{title}</h2>
      {children}
    </section>
  );
}

<Panel title="Your booking">
  <p>Two seats on Saturday.</p>
  <button type="button">Change</button>
</Panel>

Panel does not know what its content is; it provides the frame. This composition is how React reuses layout: build small generic pieces (Panel, Card, Stack) and combine them. Prefer composition over inventing many boolean props like hasHeader, hasFooter, isCompact.

Components can also receive JSX as other props (<Layout sidebar={<Filters />}>), which gives more than one slot.

7. Rendering lists of components

Data in arrays becomes components with map. Each item needs a stable key (covered fully in lesson 3):

const workshops = [
  { id: 'pottery', title: 'Pottery for Beginners', date: 'Saturday, 7 November', seatsLeft: 4 },
  { id: 'bread',   title: 'Sourdough Basics',      date: 'Sunday, 8 November',   seatsLeft: 0 },
  { id: 'print',   title: 'Linocut Printing',      date: 'Saturday, 14 November', seatsLeft: 9 }
];

function Catalog() {
  return (
    <ul className="event-grid">
      {workshops.map(w => (
        <li key={w.id}>
          <WorkshopCard title={w.title} date={w.date} seatsLeft={w.seatsLeft} />
        </li>
      ))}
    </ul>
  );
}

Expected output: three list items, with "4 seats left", "Sold out", and "9 seats left". Notice that the layout (the ul and its grid class from chapter 4) lives in Catalog, and the card's internals live in WorkshopCard.

8. Project structure

For a small app, a flat structure is enough:

src/
  main.jsx
  App.jsx
  components/
    WorkshopCard.jsx
    Panel.jsx
    Catalog.jsx
  data/
    workshops.js
  styles/
    index.css

Guidelines:

  • One component per file, file named like the component (WorkshopCard.jsx).
  • Group by feature when the app grows (features/booking/), keeping a feature's components, styles, and tests together.
  • Keep data and logic separate from presentation when it helps readability (a data/ module, helpers in utils/).
  • Import images from src/assets when the bundler should process them; place static files that must keep their names in public/.
  • Import component CSS next to the component, using class names (BEM from chapter 4) to avoid collisions, or use CSS modules (WorkshopCard.module.css), which scope class names to the file.
  • Do not over-engineer: create folders when the number of files demands it.

Component names are PascalCase; helper functions and variables are camelCase; event handler props start with on (onBook), and the functions that handle them start with handle (handleBook).

Worked example: a course-card catalog

Brief. The designer gives you three workshops and a card design: title, date, price and seats, with a "Book seats" button that is disabled when sold out. Build a reusable component and render the catalog.

Step 1: create the project. Run the Vite commands above and delete the demo content of App.jsx.

Step 2: model the data. Create src/data/workshops.js with the array shown above, adding price.

Step 3: build WorkshopCard. Decide the props from the design: title, date, price, seatsLeft. The button is part of the card.

// src/components/WorkshopCard.jsx
export default function WorkshopCard({ title, date, price, seatsLeft }) {
  const soldOut = seatsLeft === 0;
  return (
    <article className="card">
      <h3 className="card__title">{title}</h3>
      <p><time>{date}</time></p>
      <p>{price.toFixed(2)} EUR</p>
      <p>{soldOut ? 'Sold out' : `${seatsLeft} seats left`}</p>
      <button className="btn" type="button" disabled={soldOut}>
        Book seats
      </button>
    </article>
  );
}

soldOut is a derived value computed from a prop during rendering, with no extra state needed.

Step 4: build Catalog and App.

// src/App.jsx
import Catalog from './components/Catalog.jsx';

export default function App() {
  return (
    <main className="container">
      <h1>Harbor Workshops</h1>
      <Catalog />
    </main>
  );
}

Step 5: run and verify. npm run dev. You should see one h1 and three cards; the second card shows "Sold out" with a disabled button. Open DevTools to confirm the generated DOM is plain semantic HTML, then Tab through the page.

Why this design? WorkshopCard knows nothing about where its data comes from, so you can reuse it in search results, a profile page, or a test. The alternative of copying the markup three times would mean fixing every bug three times. Passing the whole workshop object as one prop is also acceptable, but explicit props make the component's needs visible. Storing soldOut in state would be worse, because it could drift out of sync with seatsLeft.

Common pitfalls

  • Lowercase component names, so <workshopCard /> is treated as an unknown HTML tag.
  • Multiple roots without a wrapper or fragment.
  • class and for instead of className and htmlFor.
  • Statements in braces: {if (x) ...} is invalid; use a ternary or &&.
  • Mutating props or variables that exist outside the component during rendering.
  • Defining a component inside another component.
  • Forgetting braces for non-string props (seatsLeft="4" passes a string).
  • Calling a component like a function (WorkshopCard(props)) instead of using <WorkshopCard />.
  • Missing key when mapping lists.
  • Following out-of-date tutorials that use Create React App or class components; prefer the current documentation.

Exam tip: Recognize valid JSX quickly: single root, closed tags, className, expressions in braces, capitalized component names. When a question asks how data moves between components, the answer is "down, via props"; children cannot modify props, and shared data lives in the nearest common parent. If a value can be computed from props, it should be computed, not stored.

Mini case study: the card that always says "0 seats left"

Situation. A teammate's card shows "0 seats left" for every workshop, even those with seats, and never shows "Sold out". The code is:

function WorkshopCard({ title, seatsLeft }) {
  const soldOut = seatsLeft = 0;
  return (
    <article>
      <h3>{title}</h3>
      <p>{soldOut ? 'Sold out' : seatsLeft + ' seats left'}</p>
    </article>
  );
}
// <WorkshopCard title="Sourdough" seatsLeft="12" />

Questions. (1) What are the bugs? (2) What does the screen show? (3) How do you fix it?

Solution.

  1. seatsLeft = 0 is an assignment, not a comparison; it reassigns the local variable (and would mutate the prop value in spirit, which props must never do) and evaluates to 0, which is falsy. Also, seatsLeft="12" passes the string "12", not a number.
  2. soldOut is 0, which is falsy, so the ternary takes the second branch, and seatsLeft has just been overwritten with 0. The screen shows 0 seats left for every card while the title stays correct.
  3. Use a comparison and a numeric prop: const soldOut = seatsLeft === 0; and seatsLeft={12}. For a robust component, validate inputs or use TypeScript types or PropTypes in larger projects. Check: with 12 the card displays "12 seats left"; with 0 it displays "Sold out".

Key terms

  • Component: a function that returns JSX and can be reused like an HTML tag.
  • JSX: a syntax extension that describes interface markup inside JavaScript.
  • Props: read-only inputs passed from a parent to a child component.
  • Children: the content nested between a component's opening and closing tags.
  • Composition: building interfaces by combining components.
  • One-way data flow: data moves down the tree through props.
  • Fragment: <>...</>, a wrapper that adds no DOM element.
  • Entry point: the file that renders the root component into the page.
  • Pure component: one that returns the same output for the same props and has no side effects while rendering.
  • Derived value: a value computed from props or state instead of stored.

Summary checklist

  • I can explain declarative rendering and why components help.
  • I can create a project with a current build tool and describe its main files.
  • I name components in PascalCase, define them at the top level, and export and import them correctly.
  • I write valid JSX: one root, closed tags, className, and braces for expressions.
  • I pass props, use destructuring and defaults, and never mutate props.
  • I explain one-way data flow and where shared data should live.
  • I use children for composition.
  • I render arrays with map and a stable key.
  • I compute derived values instead of storing them.
  • I keep the project structure simple and consistent.

Official sources