Mr. Penney / ICS4U1 / JavaScript · Algorithms · HTML & CSS · React

React Reference

React is a library for building interfaces out of components. A component is a function that returns markup. When the data it draws from changes, React redraws it.

Starting a project — lesson 14

npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev            # then open the printed http://localhost:5173

Those four lines create a new project. A lesson's folder already holds one, so a lesson starts at npm install, typed in a terminal opened from that folder; workflow.md says how. npm install is needed once per project folder, and node_modules/ is never submitted.

Path What it holds
index.html The one real HTML page. You rarely edit it
src/main.jsx Mounts the app into that page. You rarely edit it
src/App.jsx The top component. You edit this
src/components/ One file per component. You edit these
public/ Images and files served as-is

A component — lesson 14

// src/components/Greeting.jsx
export default function Greeting() {
  return (
    <section className="greeting">
      <h2>Hello</h2>
    </section>
  );
}
// src/App.jsx
import Greeting from "./components/Greeting.jsx";

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

JSX — putting values in markup

const name = "Ada";
const items = ["a", "b"];

<h1>{name}</h1>                        {/* a value */}
<p>{2 + 2}</p>                         {/* any expression */}
<img src={url} alt="" />               {/* an attribute */}
<div className={isOpen ? "open" : ""}>  {/* a choice */}
{isOpen && <p>Only when open</p>}       {/* show conditionally */}

{} holds an expression, not a statement. An if goes above the return.

Props — passing data down — lesson 14

Props go parent to child, and are read-only inside the child. A value in quotes is passed as a string, and any other value goes in braces.

// parent
<PersonCard name="Ada" born={1815} />

// child
export default function PersonCard({ name, born }) {
  return <article><h3>{name}</h3><p>Born {born}</p></article>;
}

A default value, written { name, retired = false }, is used when the parent leaves that prop out. A boolean is passed as retired={true} or as retired on its own; retired="true" passes a string, and every non-empty string is truthy.

State — lesson 15

State is a value the component redraws itself from. Changing it with set… is what triggers the redraw; assigning to a plain variable does nothing visible.

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);       // [value, setter] = useState(initial)

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}
Rule Why
Never assign to state directly (count = 5) count is a const, so the assignment stops with TypeError: Assignment to constant variable.; only the setter redraws
Call hooks at the top level of the component Never inside an if or a loop
Replace arrays and objects, never mutate them setList([...list, item]), not list.push(item)

Events and controlled inputs — lesson 15

const [query, setQuery] = useState("");

function handleSubmit(event) {
  event.preventDefault();          // stops the page reloading
  setQuery("");
}

<form onSubmit={handleSubmit}>
  <input value={query} onChange={(event) => setQuery(event.target.value)} />
  <button type="submit">Search</button>
</form>

onClick={handleClick} and onSubmit={handleSubmit} pass the function, and React calls it when the event happens. onClick={handleClick()} calls it while the component is drawn; if it sets state, React stops with Too many re-renders. An input with value and no onChange does not accept typing. A checkbox is controlled the same way, with checked={…} in place of value.

Rendering a list — lesson 15

const clubs = [
  { id: 31, name: "Chess club" },
  { id: 32, name: "Book club" }
];

<ul>
  {clubs.map((club) => (
    <li key={club.id}>{club.name}</li>
  ))}
</ul>

key goes on the outermost element the map returns, and must be unique and unchanging, so it is the item's id. The array index works only if the list is never reordered or filtered. An item added while the page runs can take Date.now() as its id.

Child to parent — lesson 15

A child cannot change a parent's state. The parent passes a function down; the child calls it.

// parent
const [booked, setBooked] = useState(0);

function bookSeat() {
  setBooked(booked + 1);
}

<BookButton onBook={bookSeat} />

// child
export default function BookButton({ onBook }) {
  return <button onClick={onBook}>Book a seat</button>;
}

A button drawn once per item passes that item's value through a new arrow function: onClick={() => chooseRoom(room.name)}. When two components need the same value, move it up to their nearest shared parent.

Effects and data — lesson 16

An effect runs code after the render: saving, fetching, starting a timer. The function given to useEffect is not async; an async function is declared inside it and called.

import { useState, useEffect } from "react";

export default function OpeningHours({ branch }) {
  const [hours, setHours] = useState("");
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    async function fetchHours() {
      try {
        const response = await fetch(`/hours-${branch}.json`);
        if (!response.ok) {
          throw new Error(`Request failed: ${response.status}`);
        }
        const opening = await response.json();
        if (!cancelled) {
          setHours(opening.text);
        }
      } catch (error) {
        if (!cancelled) {
          setHours("The hours could not be loaded.");
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }
    fetchHours();
    return () => {
      cancelled = true;          // the cleanup: a late answer for an old branch is ignored
    };
  }, [branch]);                  // runs again when branch changes

  if (loading) {
    return <p>Loading the hours…</p>;
  }
  return <p>{hours}</p>;
}
Dependency array When the effect runs
[] Once, after the first render
[branch] After the first render, and whenever branch changes
left out After every render; an effect that sets state then runs without end

fetch throws only when no answer comes back. A server's "not found" is an answer, so test response.ok. A file in the project's public folder is fetched as "/" and its name.

Saving between visits

The calls to localStorage live in their own module, so a component never uses it directly and a missing or broken saved value is handled in one place. localStorage stores only text.

// settings.js
export function saveBranch(branch) {
  localStorage.setItem("branch", JSON.stringify(branch));
}

export function loadBranch() {
  const text = localStorage.getItem("branch");   // null when nothing is saved
  if (text === null) {
    return null;
  }
  try {
    return JSON.parse(text);
  } catch (error) {
    return null;                                  // the saved text was not JSON
  }
}
// App.jsx: declare the state first, then the effect that reads it
const [branch, setBranch] = useState(() => loadBranch() ?? "Riverside");
useEffect(() => {
  saveBranch(branch);
}, [branch]);

useState(() => …) is a function initialiser, which runs only on the first render. a ?? b gives b only when a is null or undefined. Array.isArray(value) is true only for an array, for a saved value that must be a list.

Not taught in class — Level 4 material

Feature What it does
useRef A value that survives renders without causing one
useContext Share a value without passing props through every level
useReducer State transitions as a single named function
Custom hooks Your own useSomething() that reuses hook logic
React Router Multiple pages in one app
useMemo / useCallback Skip work that has not changed
setCount((c) => c + 1) A setter given a function receives the current value, so several updates in one event do not overwrite each other