πŸ“š Sanjay's Study Notes Home Roadmap HTML CSS JavaScript TypeScript Node SQL & Prisma React Tailwind Auth Apps Git & Deploy

07 Β· React + Vite β€” the UI becomes a function of the data

You already invented React's core idea by hand. Every function in your Library ends with saveBooks(); renderBooks(books); β€” change the data, then remember to repaint. React's deal: you change the data, IT repaints β€” automatically, everywhere, always.

Where it comes from Facebook, 2013. Their problem was your problem at scale: thousands of "change data β†’ update screen" pairs, and every forgotten repaint was a bug (your sorted-but-not-rerendered Task 3.2 bug β€” imagine it Γ—10,000). React's fix: describe what the UI looks like for any given data, once, and let the library keep screen and data in sync. Vite (French for "fast", say "veet") is the modern dev server + build tool that runs it all during development.

1. The one idea everything hangs on: UI = f(state)

You've already lived both halves Your renderBooks(list) IS a function from data to UI β€” pass an array, get a table. React keeps that and removes the other half of your burden: the spine rule "every function that touches books must end with save β†’ re-render" (learned painfully in Task 3.2, extended in the capstone) disappears. You update state; React notices and re-renders every affected piece. The bug class you've fought since Phase 2 β€” data changed, screen stale β€” is engineered away.

2. Vite β€” the dev setup

npm create vite@latest library-react -- --template react-ts
cd library-react
npm install
npm run dev        # β†’ http://localhost:5173, hot-reloads on every save
What Vite replaces from your workflow Your chain was edit β†’ tsc β†’ refresh (drilled the hard way). Vite runs that loop for you: save the file, the browser updates itself in under a second, TS errors appear in the terminal AND the browser. Two servers now run during dev: Vite (5173, serves the frontend) and Fastify (3000, serves the data) β€” the split from Node notes Β§9, now permanent.

3. Components β€” functions that return UI

A component is a function whose return value is markup. Your app becomes a tree of them:

function LowStockBadge() {
    return <span className="low-stock">Low stock!</span>;
}

// used like a custom HTML tag:
<LowStockBadge />

The Library's component tree β€” notice it's just your page, named:

<App>
 β”œβ”€ <SearchBox />
 β”œβ”€ <BookTable>        ← your renderBooks
 β”‚    └─ <BookRow /> Γ— N
 β”œβ”€ <AddBookForm />    ← your addBook inputs
 └─ <LoanTable>        ← your renderLoans
      └─ <LoanRow /> Γ— N
Why break UI into components Same reason code splits into functions and files into modules: name a thing once, reuse it, test it alone, find it fast. When a bug is "the loan rows show wrong dates", you open LoanRow β€” 20 lines β€” not a 500-line page.

4. JSX β€” your template literals, grown up

That HTML-in-JS is called JSX. You've been building HTML inside JS since your first render β€” with strings. JSX drops the quotes:

// Phase 2 you (string β€” browser sees it only after innerHTML):
rowsHTML += `<tr><td>${book.title}</td></tr>`;

// React you (JSX β€” real elements, type-checked):
<tr><td>{book.title}</td></tr>
Your habitJSX versionWhy different
${expr} in template literal{expr}JSX is not a string β€” no backticks, no $
class="low-stock"className="low-stock"class is a reserved JS word
onclick='borrowBook(3)' (string)onClick={() => borrow(3)} (function)real function reference β€” typo'd names become compile errors, not silent dead buttons
ternary for show/hide{loan.returned ? "Returned" : <button>...</button>}same trick, and it can return elements

5. Props β€” parameters for components

interface BookRowProps { book: Book; onBorrow: (id: number) => void; }

function BookRow({ book, onBorrow }: BookRowProps) {
    return (
        <tr className={book.copies <= 2 ? "low-stock" : ""}>
            <td>{book.title}</td><td>{book.copies}</td>
            <td><button onClick={() => onBorrow(book.id)}>Borrow</button></td>
        </tr>
    );
}
Props flow DOWN, events flow UP The parent passes data in (book) and passes a function to call when something happens (onBorrow). The row never touches the books array itself β€” it reports "id 3 was clicked" upward, and the owner of the data acts. Your data-vs-presentation line again: rows present, the owner decides. Interfaces for props = Phase 3 skills, directly reused (this is "React typing" from your gap list).

6. useState β€” where your books array goes

import { useState } from "react";

function App() {
    const [books, setBooks] = useState<Book[]>([]);

    function borrow(id: number) {
        setBooks(books.map(b => b.id === id && b.copies > 0
            ? { ...b, copies: b.copies - 1 }
            : b));
        // no saveBooks(), no renderBooks() β€” the set triggers the repaint
    }
    // ...
}
The rule that will fight your instincts: NEVER mutate state Your whole app mutates: book.copies = book.copies - 1, push, splice, sort β€” you even learned which methods mutate (JS notes, Task 3.2). React inverts it: state is read-only; to change it, build a NEW array/object and hand it to setBooks. Why: React detects change by comparing old vs new β€” mutate the old one and old === new, React sees "no change", screen stays stale. That's why the borrow above uses map + spread ({ ...b, copies: ... } = copy with one field changed) instead of assignment. Your mutate-vs-copy method knowledge just became load-bearing.
Old Library moveReact move
books.push(newBook)setBooks([...books, newBook])
books.splice(index, 1)setBooks(books.filter(b => b.id !== id))
book.copies -= 1setBooks(books.map(b => b.id === id ? { ...b, copies: b.copies - 1 } : b))
books.sort(...)setBooks([...books].sort(...)) β€” copy first, sort the copy

7. Rendering lists β€” your map, plus one new thing

<tbody>
    {books.map(book => (
        <BookRow key={book.id} book={book} onBorrow={borrow} />
    ))}
</tbody>
Why key β€” and why it's your id, again On re-render React diffs old list vs new to update only what changed. key tells it which row is which β€” identity across renders. Sound familiar? It's Task 3.5: positions lie after sorting/deleting, ids don't. Using the array index as key is the same trap as position-based delete β€” and the same fix: key={book.id}.

8. Forms β€” controlled inputs

const [title, setTitle] = useState("");

<input value={title} onChange={e => setTitle(e.target.value)} />
<button onClick={handleAdd}>Add book</button>
Controlled = state owns the box Phase 2 you: the input holds the value; you fetch it at click time with getElementById(...).value (plus the as HTMLInputElement dance). React you: state holds the value; the input just displays it and reports keystrokes. Benefits arrive fast: live validation while typing, clearing the form = setTitle(""), and no DOM digging at all. Your validation logic (trim/empty, Number() + isNaN) transfers unchanged β€” it just reads state instead of the DOM. Strings still cross the border: e.target.value is ALWAYS a string. Old enemy, same cure.

9. useEffect + fetch β€” loading books from your API

import { useEffect, useState } from "react";

function App() {
    const [books, setBooks] = useState<Book[]>([]);

    useEffect(() => {
        fetch("http://localhost:3000/books")
            .then(res => res.json())
            .then((data: Book[]) => setBooks(data));
    }, []);   // [] = run once, when the component first appears
    // ...
}
Why an "effect" and not just... code at the top? A component function re-runs on EVERY render. A fetch written directly in the body would fire again each repaint β†’ fetch β†’ setBooks β†’ repaint β†’ fetch… an infinite loop. useEffect(fn, []) fences it: "this touches the outside world (network) β€” run it once, not per render." The empty array lists what the effect depends on; empty = nothing = run only on arrival.
Three states every fetching UI has loading / loaded / failed. Real apps track them: const [loading, setLoading] = ... and show a spinner or an error message. Your Library will too β€” users on slow networks see the loading state far more often than developers on localhost do.

10. The Phase 6 arc β€” rebuild with parity

StepWhat gets built
1. Vite project runshello-world component, meet hot reload
2. Static BookTablehardcoded array β†’ table via map + key
3. State + BorrowuseState, immutable updates, the no-mutation drill
4. AddBookForm + searchcontrolled inputs, validation on state
5. Fetch from FastifyuseEffect; the API becomes the source of truth
6. Loans + fines UIcapstone features return, componentized
7. Milestonefull parity with the Phase 2/3 app β€” same features, new engine

11. The mental model to keep

IdeaOne line to remember
UI = f(state)describe the screen for any data; React repaints when data changes
Componenta function returning UI β€” your renderBooks, named and nested
JSXtemplate literals without quotes; {expr}, className, onClick={fn}
Propsdata flows down, events flow up β€” rows present, owners decide
useStatethe books array's new home; change it ONLY via the setter
No mutationbuild new arrays (map/filter/spread) β€” push/splice/sort-in-place are retired
keyidentity across renders = your book.id lesson, again
useEffectoutside-world work (fetch), fenced so it doesn't loop

← Prev: SQL & Prisma  Β·  Back to contents  Β·  Next: Tailwind + Zustand β†’