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
- Explain what React does and why components help.
- Set up a small React project with a current build tool and explain the generated structure.
- Write function components with capitalized names and export and import them.
- Write valid JSX: one root, expressions in braces,
className, self-closing tags, and camelCase attributes. - Pass data with props, use destructuring and default values, and explain one-way data flow.
- Use the
childrenprop for composition. - Explain why props are read-only.
- 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
- 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>
</>
);
- Close every tag.
<img />,<input />,<br />. - Use camelCase for most attributes, and
classNameinstead ofclass,htmlForinstead offor:<label htmlFor="email" className="field">. - 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}).
- 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. - 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 inutils/). - Import images from
src/assetswhen the bundler should process them; place static files that must keep their names inpublic/. - 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.
classandforinstead ofclassNameandhtmlFor.- 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
keywhen 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.
seatsLeft = 0is 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 to0, which is falsy. Also,seatsLeft="12"passes the string"12", not a number.soldOutis0, which is falsy, so the ternary takes the second branch, andseatsLefthas just been overwritten with0. The screen shows0 seats leftfor every card while the title stays correct.- Use a comparison and a numeric prop:
const soldOut = seatsLeft === 0;andseatsLeft={12}. For a robust component, validate inputs or use TypeScript types orPropTypesin larger projects. Check: with12the card displays "12 seats left"; with0it 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
childrenfor composition. - I render arrays with
mapand a stablekey. - I compute derived values instead of storing them.
- I keep the project structure simple and consistent.
Official sources
- Meta Front-End Developer Professional Certificate (opens in a new tab)
- Course 5: React Basics (opens in a new tab)
- React: Quick Start (opens in a new tab)
- React: Your First Component (opens in a new tab)
- React: Writing Markup with JSX (opens in a new tab)
- React: Passing Props to a Component (opens in a new tab)
- React: Build a React app from Scratch (opens in a new tab)
- React: Sunsetting Create React App (opens in a new tab)
