React Build Book
Checking

React Build Book

    1. Read the notes firstEach build points back to a section of the React introduction notes.
    2. Build alongLook at the finished model, then follow the steps in your own project.
    3. Tick off each stepFinished builds snap into the row above. Your progress is saved in this browser.

    Set up your project

    • Node.js
    • Vite
    • JSX

    Every build in this book lives in one React project on your laptop. You'll create it with Vite, a tool that sets React up for you and refreshes the page each time you save.

    Steps

    1. Check that Node.js is installed

      Open a terminal (in VS Code: Ctrl + `) and run:

      You need Node 22.12 or newer. If the command isn't found or the number is lower, install the LTS version from nodejs.org, then close and reopen the terminal.

    2. Create the project

      Go to the folder where you keep your coursework and run:

      This makes a folder called react-playground with a small React app inside. If npm asks "Ok to proceed?", type y and press Enter.

    3. Install and start it

      Open the address it prints, usually http://localhost:5173. You'll see the Vite + React demo page with a counter. Keep this terminal running while you work. Ctrl + C stops it.

    4. Look around

      Open the react-playground folder in VS Code. These are the files that matter:

      FileWhat it does
      index.htmlThe only HTML page. It has an empty <div id="root"> that React fills in.
      src/main.jsxStarts React and puts <App /> inside #root. You'll rarely touch it.
      src/App.jsxYour root component, the top of the tree. You'll edit it in every build.
      src/index.cssStyles for the whole app.
      package.jsonThe project's name, its scripts (like npm run dev) and the libraries it uses.
      node_modules/Downloaded libraries. Never edit it and never upload it.
    5. Clear out the demo

      Replace everything in src/App.jsx with this:

      Then delete src/App.css and the src/assets folder. The new App doesn't use them.

    6. Add the starter styles

      Replace everything in src/index.css with the styles below. They give your builds a clean look, so you can spend your time on React. Change them as much as you like.

    7. Save the API address

      Create a new file, src/api.js:

      If your instructor gives you a new address later, this is the only line you change.

    8. Peek at the API

      Open in a new tab and refresh it a few times. That text is JSON, and it's exactly what your React code will fetch from Build 4 on.

    Stuck?

    1. "npm is not recognized" or "command not found": Node isn't installed, or the terminal was opened before you installed it. Close it and open a new one.

    2. "Port 5173 is in use": Vite picks the next free port, like 5174. Use whichever address the terminal prints.

    3. A blank white page: open DevTools (F12), go to the Console tab and read the first red error. It names the file and the line.

    Quick check

    npm run dev is running and you save a change to App.jsx. What do you need to do to see it?

    Vite watches your files. When you save, it swaps the changed code into the open page. You only restart it if you stop the terminal.

    Page skeleton

    • Components
    • JSX
    • import / export

    Cut the page into boxes. You'll make a Header and a Footer component and snap them into App. Every later build goes in the space between them. Notes: sections 3 and 4.

    Steps

    1. Make a components folder

      Inside src, create a folder called components. Every component you build goes in here, one file each.

    2. Build the Header

      A component is a function that returns JSX. Its name starts with a capital letter, and export default lets other files import it. In JSX you write className where HTML uses class.

    3. Build the Footer

      Put your own name in. Anything inside { } in JSX is plain JavaScript, so {year} shows the current year. It's worked out once, outside the component, because it doesn't change while the page is open.

    4. Snap them into App

      Once a component is imported, you use it like a custom HTML tag: <Header />.

    5. Check the browser

      You should see the header at the top and the footer at the bottom, like the finished model. From now on, each build goes inside <main>.

    Stuck?

    1. Does each component file end with export default ComponentName;?

    2. Import paths start from the file you're in. From App.jsx it's ./components/Header, with no .jsx needed.

    3. Blank page? Open the Console (F12). The red error usually names the file and the line.

    Show the solution Give it a real try first.

    The Header, Footer and App code in the steps above is the full solution. Here's the level-up:

    Quick check

    Why do we write <Header /> in App and not <header />?

    React looks at the first letter. <header> becomes a real HTML header tag; <Header> runs your Header function.

    Profile cards

    • Props
    • Lists and keys
    • Components

    One card design, many people. Props are the inputs you hand a component, so a single ProfileCard can show Riya, then Arjun, then Meera. Notes: section 5.

    Steps

    1. Look at the data

      Open . Each person has an id, name, role, city and an avatar image address. Keep the tab open; you'll copy from it.

    2. Make a card that takes props

      { name, role, city, avatar } pulls each prop out of the props object, so you can write name instead of props.name.

    3. Use it three times

      Text props go in quotes. Everything else (numbers, variables, template strings) goes in curly braces. Then show it in App:

    4. Turn the copies into a list

      Three nearly identical blocks is a sign you want an array and .map(). Replace ProfileGallery.jsx with:

      key helps React tell the cards apart when the list changes. Use something unique that doesn't change, like an id.

    5. Add a fourth person

      Copy Kabir Khan from the API into the people array. One new object gives you one new card, and you didn't write any new JSX. That's the point of props.

    Stuck?

    1. Props are written like HTML attributes, <ProfileCard name="Riya Sharma" />, and arrive in the card as { name }.

    2. A string with a variable inside uses backticks: `${API_URL}/api/avatars/riya-sharma.svg`.

    3. .map() has to return the JSX. With => ( … ) the return is automatic. With => { you need to write return.

    Show the solution Give it a real try first.

    ProfileCard.jsx and the list version of ProfileGallery.jsx in the steps are the solution. Here's the card with both level-ups:

    Quick check

    A ProfileCard wants to change the name it was given when someone clicks it. What's the right move?

    Props are like your name on a birth certificate: you use it, you don't rewrite it. Changing data is state, which you'll meet in the next build.

    A joke card with a memory

    • useState
    • Events
    • Showing things conditionally

    This card remembers two things: whether its punchline is showing, and how many likes it has. That memory is state. When it changes, React redraws the card for you. Notes: sections 6 and 7.

    Steps

    1. Start with a static card

      Make it look right first, with props only and no interaction (step 3 of Thinking in React).

      Show two of them in App:

    2. Give it a memory

      Read it aloud: "I need a piece of state called showPunchline, starting at false, and setShowPunchline is how I'll change it."

    3. Hide the punchline until clicked

      a && b shows b only when a is true. a ? b : c picks one of two things. Both are plain JavaScript.

    4. Count likes

      Add a second piece of state, and a second button next to the first inside a <div className="row">:

    5. Prove each card has its own memory

      Like the first card three times. The second one still says 0. Every JokeCard you place gets its own state.

    Stuck?

    1. Import the hook at the top: import { useState } from "react";

    2. Give onClick a function: onClick={() => setLikes(likes + 1)}. Writing onClick={setLikes(likes + 1)} calls it straight away during drawing, which causes a "Too many re-renders" error.

    3. !showPunchline flips true to false and back.

    4. Adding a class only sometimes: className={liked ? "btn btn-secondary liked" : "btn btn-secondary"}.

    Show the solution Give it a real try first.

    Quick check

    You click ❤️ three times on the first JokeCard. What does the second JokeCard show?

    A component is a blueprint. Each <JokeCard /> you place is a separate copy with its own memory.

    Random joke machine

    • useEffect
    • fetch
    • Loading state

    Your first call to the API. The card loads a random joke as soon as it appears, and a button fetches another. Fetching is a side effect: work that reaches outside the screen. Notes: section 8.

    Steps

    1. Set up the component

      fetchRandomJoke sits outside the component because it doesn't need any state. joke starts as null: nothing has arrived yet.

    2. Load a joke when the card appears

      The empty list [] means "run once, after the component first appears on screen".

    3. Show "Loading", then the joke

      The first time the component draws, joke is still null, so it shows the loading message. When the data arrives, setJoke triggers a redraw with the real joke.

    4. Make the button fetch another

      Add this inside the component, above the if:

      Clearing the joke first brings the loading message back while the next one is on its way. Then put <RandomJoke /> in App's <main>.

    5. Watch it happen

      Add ?delay=2000 to the address in fetchRandomJoke to make the API wait two seconds, so you can see the loading message. Open DevTools → Network, click the button, and click the request to see the JSON. Take the delay out when you're done.

    Stuck?

    1. Import both hooks: import { useEffect, useState } from "react";

    2. Cannot read properties of null (reading 'setup') means you read joke.setup before the joke arrived. Return the loading message while joke is null.

    3. Failed to fetch usually means a wrong address. Check API_URL in src/api.js: no slash at the end, and the same address as the one at the top of this page.

    4. For the error level-up:

    Show the solution Give it a real try first.

    Quick check

    Why does the fetch go inside useEffect instead of straight in the component body?

    The component function runs on every redraw. An effect with [] runs once, after the first draw, which is exactly when you want to start loading.

    Weather by city

    • Dependency list
    • Controlled inputs
    • fetch

    Pick a city, get its weather. The effect has to run again every time the city changes, and the dependency list is how you tell React. Notes: section 8, "When does an effect run again?"

    Steps

    1. Three pieces of state

    2. Load the list of cities once

    3. Load the weather whenever the city changes

      [city] means "run after the first render, and again whenever city is different from last time". Two effects is fine: each one does one job.

    4. Add the dropdown

      value={city} plus onChange makes this a controlled input: state decides what the dropdown shows, and every change goes through state.

    5. Show the weather

      Replace the comment with:

      Put <WeatherWidget /> in App and switch between cities.

    Stuck?

    1. Inside onChange, the chosen city is event.target.value.

    2. Stuck on "Loading weather…"? Check that the second effect's list is [city].

    3. Empty dropdown? Check the first effect calls setCities, and that each <option> has a key and a value.

    Show the solution Give it a real try first.

    Quick check

    A weather widget should load new weather every time the selected city changes. What goes in its dependency list?

    List what the effect reads that can change: here, city. [weather] would loop, because the effect itself sets weather.

    Search the jokes

    • Lifting state up
    • Functions as props
    • Calculated values

    A search box and a list are siblings, and siblings can't pass props to each other. So the search text lives in their parent and flows down to both. Notes: section 6, "Where should state live?"

    Steps

    1. Plan who owns what

      JokeSearch owns jokes and query ├─ SearchBar gets query, and onQueryChange to ask for changes └─ JokeList gets only the jokes that match

      Data flows down as props. Requests to change it go up through a function the parent passes down.

    2. Build the search bar

      SearchBar can't change query itself, because it doesn't own it. It calls onQueryChange, and the parent does the changing.

    3. Build the list

    4. Build the parent

      visibleJokes is not state. It's worked out from jokes and query on every render, so it can never fall out of sync.

    5. Try it

      Put <JokeSearch /> in App and type "bug", then "state". The count and the list update together on every key press.

    Stuck?

    1. You can pass the setter itself as the prop: onQueryChange={setQuery}.

    2. "Hello".toLowerCase().includes("ell") is true. Lower-casing both sides makes the search ignore capitals.

    3. An empty search matches everything, because every string includes "".

    4. Filter by category first: category === "all" || joke.category === category.

    Show the solution Give it a real try first.

    The three files in the steps are the full solution.

    Quick check

    SearchBar and JokeList both need the search text. Where should it live?

    State lives in the lowest component that every user of it sits under. Two copies kept in sync is exactly the "out of sync" bug from section 1 of the notes.

    Campus store

    • Arrays in state
    • Replace, don't modify
    • Calculated values

    Add snacks and stationery to a cart and see the total. You'll practise the habit from section 7 of the notes, replace, don't modify, and see why the total is not state.

    Steps

    1. Load and show the products

      It won't work until the next step adds addToCart. Put <CampusStore /> in App.

    2. Add to the cart, the right way

      Add this inside the component, above the return:

      [...cart, x] makes a brand-new array with everything from cart plus x. { ...product, quantity: 1 } makes a new object: a copy of the product with one extra field.

    3. Same item twice? Raise the quantity

      map builds a new array. The matching item is swapped for a new object with quantity + 1; everything else is kept as it is. The old array is never touched.

    4. Show the cart and let items be removed

      Then add the cart next to the grid, inside <section className="store">:

    5. Work out the total

      Above the return:

      And at the bottom of the cart: <p className="cart-total">{itemCount} items, ₹{total}</p>.

      The total is calculated from cart on every render. If it were its own state, you'd have to remember to update it in add, in remove, and in every feature you add later.

    Stuck?

    1. [...cart] copies an array and { ...item } copies an object. Put your change after the copy: { ...item, quantity: 2 }.

    2. .some() answers yes or no: is there any item that matches?

    3. reduce starts at 0 (its last argument) and adds each item's amount to the running sum.

    Show the solution Give it a real try first.

    Quick check

    In a shopping cart, should the total price be its own piece of state?

    If you can work a value out from other data, it isn't state. Calculating it means it can never disagree with the cart.

    Quote of the moment

    • Effect cleanup
    • Timers
    • Dependency list

    A quote that changes every ten seconds, with a pause button. A timer keeps going until someone stops it, so this build is about cleaning up after yourself: when you leave a room, switch off the fan. Notes: section 8, "Cleaning up".

    Steps

    1. Two pieces of state

    2. Start a timer, and clean it up

      The function you return is the cleanup. React calls it before running the effect again (when paused changes) and when the component leaves the screen.

    3. Show the quote and a pause button

    4. Watch the timer in the Network tab

      Put it in App and open DevTools → Network. A request every ten seconds. Pause: they stop. Resume: one straight away, then every ten seconds again.

    Stuck?

    1. setInterval returns an id. clearInterval(id) stops that one timer.

    2. Return the cleanup from the effect itself, not from loadQuote.

    3. if (paused) return; leaves the effect early, so no timer starts while paused.

    Show the solution Give it a real try first.

    And the clock from the level-up:

    Quick check

    An effect starts a setInterval but never returns a cleanup. What happens when the effect runs again?

    React doesn't know what your effect started. Only your cleanup can stop it.

    Capstone: Joke Hub

    • Components
    • Props
    • State
    • Lifting state up
    • Effects

    Put every brick together. This time there's no step-by-step code: you get the requirements, a suggested component tree and some hints. Tick each requirement off as you meet it, then show your Hub to your instructor.

    Requirements

    1. Category buttons

      Load and show a chip button for each one, plus "any". The selected one has the active class.

    2. A joke from the chosen category

      A "Get a joke" button loads /api/jokes/random?category=food (or whichever is selected). With "any", leave the category out.

    3. Hidden punchline

      Show the joke with your JokeCard from Build 3, so the punchline stays hidden until clicked. A new joke starts hidden again.

    4. Save favourites

      A "⭐ Save" button adds the current joke to a favourites list under it. Saving the same joke twice does nothing. Each favourite has a Remove button.

    5. Count in the header

      The Header shows how many jokes are saved, like "⭐ 3 saved".

    6. Favourites survive a refresh

      Save a few, reload the page, and they're still there.

    Suggested component tree

    App owns favourites ├─ Header gets the number of favourites ├─ JokeHub owns categories, category and joke; gets onSave │ └─ JokeCard from Build 3 ├─ Favourites gets favourites and onRemove └─ Footer

    Stuck?

    1. Where does each piece of state live? Ask who needs it. Header, JokeHub and Favourites all need favourites, so it lives in App and goes down as props, along with functions to change it.

    2. Fetching a joke happens because of a click, so it belongs in the click handler. You only need an effect for loading the categories when the Hub first appears.

    3. Skip duplicates: if (favourites.some((saved) => saved.id === joke.id)) return;

    4. A new joke should start with its punchline hidden. Give the card a key: <JokeCard key={joke.id} … />. A new key tells React it's a different card, so its state starts fresh.

    5. Saving to the browser uses localStorage, which only stores text, hence JSON:

      The function you pass to useState runs only on the very first render, so the saved list is read once.

    More to build on your own

    • Team directoryFetch every profile and filter by skill with chips. Uses /api/profiles.
    • Three-city dashboardGive WeatherWidget a city prop and show three side by side.
    • Show / hide passwordOne true/false state that switches an input between password and text.
    • To-do listThe walkthrough from section 9 of the notes, saved with localStorage. No API needed.

    Class API

    Every endpoint answers GET requests with JSON, so you can open any of them in the browser. Lists come back as plain arrays; single things come back as objects. Open one and press Send request to see the answer.

    When something breaks

    First, open DevTools (F12) and read the first red message in the Console. Then find it below.

    "Failed to fetch", or a CORS error

    The browser couldn't reach the API. Check API_URL in src/api.js: it should match the address at the top of this page exactly, with no slash at the end. If the dot next to it is red, the API itself is down, so tell your instructor.

    If the API is on a free host, it may fall asleep when nobody uses it. The first request can then take up to a minute; after that it's quick.

    Cannot read properties of null (reading '…')

    You're reading from data that hasn't arrived yet. On the first render your state is still null. Return a loading message while it's null, as in Build 4.

    Objects are not valid as a React child

    You put a whole object in JSX, like {joke}. React can show text and numbers, not objects. Show a field instead: {joke.setup}.

    Each child in a list should have a unique "key" prop

    Every element you return from .map() needs key={something unique}, usually an id from the data.

    Too many re-renders

    You're calling a setter while drawing, often onClick={setLikes(likes + 1)}. Wrap it in a function: onClick={() => setLikes(likes + 1)}.

    I changed a variable but the screen didn't update

    Only state changes made through a setter make React redraw. For arrays and objects, hand the setter a new copy instead of changing the old one (Build 7).

    My effect runs over and over

    Check the dependency list. No list means "after every render". If the effect sets a piece of state that's also in its list, it triggers itself.

    Every request shows up twice in the Network tab

    That's React's development mode running effects twice on purpose, to catch missing cleanups. It's normal, and it doesn't happen in a built app.

    The page is blank and there's no error

    Check that the terminal running npm run dev is still open, that you're on the address it printed, and that App.jsx ends with export default App;.

    React Build Book. Your ticks are saved in this browser only, so they won't follow you to another computer.