Module 4 · React · Deep Dive

React Fundamentals

From poking the DOM with jQuery to describing it with state — components, props, useState, events, and lists, the way React actually wants you to think.

BasicIntermediateBuild

Why this matters The DocChat frontend — the document list, the upload form, the chat window — is React (then Next.js on top). You already know the DOM cold from jQuery, so the hard part isn't the syntax; it's the mental flip. Once "UI is a function of state" clicks, the rest is small. This lesson makes it click, then builds a real DocChat piece you keep.
In this lesson
  1. The mental shift: imperative → declarative
  2. Setup: Vite & the dev server
  3. JSX rules
  4. Components, props & composition
  5. State with useState & events
  6. Lists, keys & conditional rendering
  7. Build: DocList + AddDoc
  8. Check yourself

1 · The mental shift

This is the whole lesson in one idea, so slow down here. In jQuery you command the DOM, step by step: find an element, change it, find another, change that. You own the sequence of mutations.

// jQuery — imperative: you do every step by hand
$("#add").on("click", function () {
  const name = $("#name").val();
  $("#list").append("<li>" + name + "</li>");  // poke the DOM
  $("#name").val("");                          // poke it again
});
The flip In React you never touch the DOM. You hold state (the data), and you write a function that says "given this state, here's what the screen should look like." When state changes, React re-runs that function and updates the real DOM for you, efficiently. You describe the destination; React figures out the steps.
// React — declarative: describe the UI for the current state
function List({ docs }) {
  return <ul>{docs.map(d => <li key={d}>{d}</li>)}</ul>;
}
// Add a doc to state → React re-renders the list. You never call append().
jQuery bridge: stop thinking "find the node and change it." Start thinking "change the data; the view follows." The view is downstream of state, always.
The one-line definition (say it in interviews) UI = f(state). The screen is a pure function of your state and props. Same state in → same UI out. That sentence signals you actually understand React, not just its syntax.

2 · Setup: Vite & the dev server

In 2026, Vite is the standard way to spin up a plain React app (Create React App is dead). One command scaffolds everything:

# scaffold — pick "React", then "JavaScript" (or "TypeScript" later)
npm create vite@latest docchat-ui

cd docchat-ui
npm install
npm run dev        # dev server with instant hot-reload at localhost:5173

The dev server watches your files and hot-reloads the browser on save — no manual refresh. Your app starts in src/main.jsx, which mounts a root component (App) into one <div id="root"> in index.html. That single div is the only DOM you ever write by hand.

3 · JSX rules

JSX is HTML-looking syntax inside JavaScript. It's not a string and not HTML — it compiles to function calls. A handful of rules trip up every newcomer:

function Greeting() {
  const name = "Sam";
  return (
    <>                                  {/* Fragment = one root, no extra div */}
      <h1 className="title">Hi {name}</h1>
      <img src="/logo.png" alt="logo" />   {/* self-closed */}
      <p>{2 + 2} docs loaded</p>          {/* expression */}
    </>
  );
}
jQuery bridge: comments inside JSX use {/* ... */}, not <!-- -->. And attributes are camelCase: onClick, tabIndex.

4 · Components, props & composition

A component is just a function that returns JSX. Its name must be PascalCase — that's how React tells your components apart from plain HTML tags. You render one by writing it like a tag: <DocCard />.

Props are the inputs — a component receives one object of them and treats it as read-only. Never mutate props; they flow down from parent to child.

// A presentational component. `doc` is a prop.
function DocCard({ title, pages }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <p>{pages} pages</p>
    </div>
  );
}

// A parent composes children, passing props in.
function App() {
  return <DocCard title="Intro" pages={12} />;
}

Composition is how you build up a UI from small parts. The special prop children holds whatever you nest between a component's tags — great for wrappers like panels and layouts:

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

<Panel><p>Anything here lands in `children`.</p></Panel>
jQuery bridge: a component ≈ a reusable PHP include/partial that takes arguments — except it lives in the browser and re-renders itself when its data changes.

5 · State with useState & events

Props come from above and are read-only. State is data a component owns and can change over time. You declare it with the useState Hook:

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);   // [value, setter]
  return <button onClick={() => setCount(count + 1)}>
    Clicked {count}
  </button>;
}

useState(initial) returns a pair: the current value and a setter. The re-render model is the key idea: calling the setter doesn't just change a variable — it tells React "this state changed," and React re-runs the component function to produce fresh JSX with the new value. State change → re-render. Always.

Never assign state directly Writing count = count + 1 or docs.push(x) does nothing visible — React doesn't know anything changed and won't re-render. Always go through the setter, and pass a new array/object, never a mutated one: setDocs([...docs, x]).

Events are camelCase props that take a function: onClick, onChange, onSubmit. For inputs, you wire value + onChange together to make a controlled input — React state is the single source of truth for what's typed:

function NameField() {
  const [name, setName] = useState("");
  return (
    <input
      value={name}                              // state drives the input
      onChange={(e) => setName(e.target.value)}  // input updates state
    />
  );
}
jQuery bridge: no $("#name").val() to read the field. The value is name in state — you already have it. The input and state stay in lockstep.

6 · Lists, keys & conditional rendering

To render a collection, .map it into an array of JSX elements. Each element needs a stable, unique key prop:

function DocList({ docs }) {
  return (
    <ul>
      {docs.map((doc) => (
        <li key={doc.id}>{doc.title}</li>
      ))}
    </ul>
  );
}
Interview — why keys matter: keys let React match each item to its DOM node across re-renders, so it can move/update only what changed instead of rebuilding the whole list. Use a stable id, never the array index when the list can reorder or have items removed — that causes wrong-item bugs and lost input state.

Conditional rendering uses plain JS inside {}. Two common forms — && for "show or nothing", a ternary for "show A or B":

{docs.length === 0 && <p>No documents yet.</p>}

{loading
  ? <p>Loading…</p>
  : <DocList docs={docs} />}
The && zero trap {docs.length && <List />} renders a literal 0 when the list is empty, because 0 is falsy and gets printed. Compare explicitly: {docs.length > 0 && …}.

7 · Build it

Your tangible win Build the first real DocChat piece: a <DocList> that shows documents, and a controlled <AddDoc> form that adds a new one to local state. This is the lift-and-state pattern every form in the app will use.

Try it before peeking. A clean, runnable version:

src/App.jsx
import { useState } from "react";

function DocList({ docs }) {
  if (docs.length === 0) return <p>No documents yet.</p>;
  return (
    <ul>
      {docs.map((doc) => (
        <li key={doc.id}>{doc.title}</li>
      ))}
    </ul>
  );
}

function AddDoc({ onAdd }) {
  const [title, setTitle] = useState("");

  function handleSubmit(e) {
    e.preventDefault();              // stop the page reload
    const clean = title.trim();
    if (!clean) return;
    onAdd(clean);                    // hand the value up to the parent
    setTitle("");                    // reset the controlled input
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="New document title"
      />
      <button type="submit">Add</button>
    </form>
  );
}

export default function App() {
  const [docs, setDocs] = useState([
    { id: 1, title: "Intro.pdf" },
    { id: 2, title: "Report.pdf" },
  ]);

  function addDoc(title) {
    const id = Date.now();              // quick unique id
    setDocs([...docs, { id, title }]);  // NEW array, never push()
  }

  return (
    <div>
      <h1>DocChat — Documents</h1>
      <AddDoc onAdd={addDoc} />
      <DocList docs={docs} />
    </div>
  );
}

Notice the shape: App owns the state; AddDoc is a controlled form that lifts the value up via an onAdd callback; DocList is purely presentational. State lives in the common parent so both children share it. You'll repeat this exact pattern across DocChat.

8 · Check yourself

Answer from memory first — retrieval is what turns "I read it" into "I know it".

Recall quiz

React's core model is best summed up as:

What does calling a useState setter trigger?

Inside JSX, you write a CSS class with:

Why does each mapped list item need a key?

A controlled input keeps its value in:

Primary source ⭐ react.dev — Learn React. The official, 2026-current docs. Work through "Describing the UI", "Adding Interactivity", and "Thinking in React" — they map almost one-to-one onto this lesson.