Theory
How a function component remembers
A React function component is just a function: it runs, returns JSX, and forgets everything. So how does a counter REMEMBER its count between clicks? How does FestConnect FETCH its events once when the page loads?
The answer is hooks: special functions that give function components MEMORY and lifecycle powers that once required class components. You already met one, useState, holding form values. This lesson covers the hook family, the rules that govern them, and how to bundle your own logic into CUSTOM hooks. Hooks are modern React's core, and they complete FestConnect's front-end.
Theory
useState and useEffect: the two you use daily
useState adds STATE to a component:
const [count, setCount] = useState(0);
count is the current value, setCount updates it, and calling setCount triggers a RE-RENDER with the new value. State is a component's own changeable data (unlike read-only props).
useEffect runs SIDE EFFECTS (things outside rendering: fetching data, timers, subscriptions) AFTER the component renders. Its second argument, the dependency array, controls WHEN it runs:
[](empty): run ONCE, on mount: perfect for fetching FestConnect's events on load[x]: run wheneverxchanges- omitted: run after EVERY render (rarely what you want)
These two cover most real components: state to remember, effects to reach the outside world.
Practical
useState (counter) and useEffect (fetch on mount)
function EventPage() {
const [count, setCount] = React.useState(0); // state
const [events, setEvents] = React.useState([]);
// useEffect with [] runs ONCE, after the first render:
React.useEffect(() => {
fetch("/api/events") // hits the MongoDB-backed API
.then(r => r.json())
.then(data => setEvents(data)); // updates state -> re-render
}, []); // [] = run once on mount
return (
<div>
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
<p>{events.length} events loaded</p>
</div>
);
}
This example runs in Gri-Learn on the web, where you can edit it and see the output.
At a glance
The hook family
| Hook | What it does |
|---|---|
| useState | Adds state; the setter triggers a re-render |
| useEffect | Runs side effects after render; dependency array controls timing |
| useContext | Reads shared context without passing props down every level |
| useRef | A value that persists across renders WITHOUT re-rendering (or a DOM reference) |
| useReducer | State via a reducer function, for complex state logic |
| useCallback / useMemo | Memoize a function / a computed value to avoid needless work |
Theory
Rules of hooks, and custom hooks
Hooks have 2 unbreakable rules:
- call hooks only at the TOP LEVEL of a component, never inside loops, conditions, or nested functions (React relies on hooks being called in the same order every render)
- call hooks only from React function components (or other hooks)
Custom hooks are your own reusable logic: a function whose name starts with use that calls other hooks. For example, a useEvents() custom hook could bundle the fetch-events state and effect, so any component can const events = useEvents(); and get the data. Custom hooks are how you SHARE stateful logic across components without repeating it: the main advantage hooks brought over the old class approach.
Quiz
You want FestConnect to fetch its events exactly ONCE when the page first loads. Which useEffect dependency array do you use?
- No second argument (runs after every render)
- An empty array []: the effect runs once, on mount
- [events]: it runs whenever events changes
- useEffect cannot control when it runs
Show the answer
An empty array []: the effect runs once, on mount
An empty dependency array [] tells useEffect to run its effect exactly ONCE, after the first render (on mount): ideal for a one-time fetch of the events. Option A (no array) runs the effect after EVERY render, which would re-fetch endlessly (and worse, setEvents triggers a render, causing an infinite fetch loop). Option C ([events]) would re-run whenever events changes, and since the fetch SETS events, that also loops. Option D is false: the dependency array is precisely how you control timing. The rule: [] for once-on-mount, [x] for when-x-changes, omitted for every-render (rarely wanted). Fetch-on-load is the classic [] case.
Think first
The infinite fetch loop
A student writes useEffect to fetch events but FORGETS the dependency array entirely. The network tab shows the same request firing endlessly. Trace why. Then tap.
Show the answer
Without a dependency array, useEffect runs after EVERY render. The effect fetches events and calls setEvents(data). But setEvents UPDATES STATE, which triggers a RE-RENDER. The re-render runs useEffect AGAIN (no dependency array = every render), which fetches again, which setEvents again, which re-renders again: an infinite loop hammering the server. The fix is the empty dependency array []: it tells useEffect to run ONCE on mount and then never again, breaking the loop. This is one of the most common React beginner bugs, and it teaches the dependency array's purpose vividly: it controls WHEN the effect re-runs, and forgetting it means 'always', which combined with state updates spirals. Always give useEffect the right dependency array.
Watch out
Hooks traps
Missing/wrong dependency array: no array = every render (infinite loops with fetch); [] = once; [x] = when x changes.
Hooks in conditions or loops: breaks the rules of hooks; call them at the top level only.
Mutating state directly: use the setter (setCount), never count = ...; direct mutation does not re-render.
Stale closures: an effect capturing an old state value: include the right dependencies.
Custom hook not starting with 'use': the name must start with use for the rules to apply.
Theory
Unit 2 complete: React front-end built
FestConnect now has a modern React front-end: components and props, events, lists, conditional rendering, controlled forms, and hooks for state, effects and reusable logic, sitting on the MongoDB back-end from Unit 1. That is one complete modern stack. Unit 3 teaches the OTHER major front-end framework, Angular, so you can compare the two industry leaders. Different philosophy, same goal: React was a library of components; Angular is a full framework, next.
Summary
Key takeaways
- Hooks are functions (names start with 'use') that give function components state and lifecycle powers.
- useState adds state: [value, setValue] = useState(initial); the setter triggers a re-render.
- useEffect runs side effects after render; the dependency array controls timing: [] once, [x] on change, omitted every render.
- The family: useContext (shared context), useRef (persist without re-render / DOM ref), useReducer (complex state), useCallback/useMemo (memoize).
- Rules of hooks: call only at the top level, only from React functions.
- Custom hooks (functions starting with 'use' that call hooks) reuse stateful logic across components.
- Memory hook: useState remembers, useEffect reaches outside, [] runs once.