Checkpoint: React Fundamentals
Reviewed & published by Brayan K
This checkpoint is a review milestone that consolidates the React Fundamentals track — JSX, props, fragments, rendering, styling, Strict Mode, effect timing, and immutable state — through a hands-on build challenge and a short quiz. It confirms you can combine these ideas before moving on to deeper hooks.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Practice
- Recall the core fundamentals in one place
- Keep render pure and predictable
- Pass data down with read-only props
- Update arrays and objects immutably
- Build a small to-do model step by step
- Self-check with a checkpoint quiz
What You've Learned So Far
JSX In Depth — JSX is sugar for createElement objects; {} embeds expressions; attributes are camelCase; return one root.
Props & One-Way Data Flow — read-only inputs flow down; events flow up via callbacks; children wraps content.
Fragments — group siblings with <>...</> and add zero DOM nodes.
How Rendering Works — render builds a virtual DOM; reconciliation diffs it; only changes are committed; a re-render is not always a DOM update.
Styling — className, camelCase inline style objects, scoped CSS Modules, conditional classes.
Strict Mode — dev-only double-invoke surfaces impurity and missing cleanup.
useLayoutEffect vs useEffect — before paint vs after paint; measure-and-adjust layout in the pre-paint hook.
Immutable State Updates — never mutate; copy with spread; add/remove/update arrays with spread/filter/map.
// WARM-UP: prove you remember the four big ideas with plain JS.
// 1) Pure render: same input -> same output.
function fullName(p) { return p.first + " " + p.last; }
// 2) Immutable update: a NEW object, original untouched.
function rename(p, first) { return { ...p, first }; }
const ada = { first: "Ada", last: "Lovelace" };
console.log(fullName(ada)); // Ada Lovelace
const grace = rename(ada, "Grace");
console.log(fullName(grace)); // Grace Lovelace
console.log(ada.first); // Ada <- original safe🛠️ Build Challenge: A Tiny To-Do Model
Implement four pure, immutable helpers for a to-do list, then run the three steps. None of your helpers may mutate the list they receive — each returns a brand-new array. This mirrors exactly how you would update React state.
1. addTodo(list, text) — append { id, text, done: false } using spread.
2. toggleTodo(list, id) — map the list, flipping done on the match.
3. removeTodo(list, id) — filter out that id.
4. summary(list) — return "DONE/TOTAL done".
// 🎯 BUILD CHALLENGE (starter) — a tiny immutable to-do model.
//
// Implement these PURE helpers (no mutation of the input list):
// addTodo(list, text) -> new list with { id, text, done:false } appended
// toggleTodo(list, id) -> new list with that item's done flipped
// removeTodo(list, id) -> new list without that item
// summary(list) -> "DONE/TOTAL done" e.g. "1/2 done"
//
// STEP 1: add two todos. STEP 2: toggle the first. STEP 3: remove the second.
//
// ✅ Expected final output:
// 1/1 done
let nextId = 1;
function addTodo(list, text) {
// your code: return [...list, { id: nextId++, text, done: false }]
return list;
}
function toggleTodo(list, id) {
// your code: map, flipping done on the match
return list;
}
function removeTodo(list, id) {
// your code: filter out the id
return list;
}
function summary(list) {
// your code: count done vs total
return "";
}
let todos = [];
todos = addTodo(todos, "Learn JSX");
todos = addTodo(todos, "Learn props");
todos = toggleTodo(todos, 1);
todos = removeTodo(todos, 2);
console.log(summary(todos)); // should print: 1/1 donelet nextId = 1;
function addTodo(list, text) {
return [...list, { id: nextId++, text, done: false }]; // spread = new array
}
function toggleTodo(list, id) {
return list.map(t => t.id === id ? { ...t, done: !t.done } : t); // copy match
}
function removeTodo(list, id) {
return list.filter(t => t.id !== id); // new array, item gone
}
function summary(list) {
const done = list.filter(t => t.done).length;
return done + "/" + list.length + " done";
}
let todos = [];
todos = addTodo(todos, "Learn JSX"); // [#1]
todos = addTodo(todos, "Learn props"); // [#1, #2]
todos = toggleTodo(todos, 1); // #1 done
todos = removeTodo(todos, 2); // drop #2
console.log(summary(todos)); // 1/1 done
// Why it works: every helper returns a NEW array/object, so React would
// see a changed reference and re-render — and the originals stay intact.🧩 Reorder Challenge
Order the lifecycle of one state update, the thread running through this whole track:
A. React reconciles the new tree against the old
B. You call a setter with a NEW (immutable) value
C. Effects run after the browser paints
D. React re-renders the component (pure)
E. Only the diff is committed to the DOMCommon Errors (and the fix)
- Helper mutates the list. — Using push/splice breaks purity. Return a new array with spread/filter/map.
- Two siblings, no wrapper. — Group them in a fragment <>...</>.
- Reading props.x when it is undefined. — Add a default or guard; props are inputs you do not control.
- Side effect in render. — Keep render pure; move effects into useEffect with cleanup.
📋 Quick Reference
| Idea | In one line |
|---|---|
| JSX | sugar for createElement; one root |
| Props | read-only, flow down; events flow up |
| Rendering | diff then commit only changes |
| Effects | useEffect after paint; clean up |
| State | update immutably (new value) |
🧠 Quick Recall
1. What does JSX compile to?
React.createElement calls returning plain element objects.
2. Which way does data flow?
Down via props; events flow back up via callback props.
3. Why update state immutably?
React compares by reference; a new value is how it detects the change and re-renders.
🔎 Predict-the-Output Self-Test
Guess each line before running it. This checks that reference equality and shallow copies really clicked.
// 📝 SELF-TEST: predict the output BEFORE you run it.
// This exercises immutability + reference equality.
const a = [1, 2, 3];
const b = a; // same reference
const c = [...a]; // new array
b.push(4); // mutates a too!
console.log(a.length); // ?
console.log(a === b); // ?
console.log(a === c); // ?
// ✅ Expected output:
// 4
// true
// false📝 Checkpoint Quiz
1. Why must a component return one root element?
The return is a single expression compiling to one element. Group siblings in a fragment <>...</>.
2. A child needs to tell its parent a value changed. How?
The parent passes a callback prop; the child calls it. Data flows down, events flow up.
3. Does every re-render touch the DOM?
No. React diffs the new tree and commits only the changes; an unchanged tree leaves the DOM alone.
4. You write items.push(x) then setState and nothing updates. Why?
Same reference — React bails out. Pass a new array: [...items, x].
5. When does useLayoutEffect run relative to paint?
Synchronously before paint (useEffect runs after). Use it only for measure-and-adjust layout.
6. Why does an effect sometimes run twice in development?
Strict Mode double-invokes (run, cleanup, run) to surface missing cleanup. It is dev-only.
🎉 Checkpoint Complete
- ✅ Reviewed every React Fundamentals idea in one place
- ✅ Built immutable to-do helpers with spread, map, filter
- ✅ Confirmed references vs copies with the self-test
- ✅ Passed the checkpoint quiz on rendering, props, and effects
Practice quiz
What does JSX compile down to?
- Raw HTML strings inserted into the DOM
- Template literals evaluated at runtime
- React.createElement calls that return plain element objects
- A separate .html file loaded by the bundler
Answer: React.createElement calls that return plain element objects. JSX is syntactic sugar for React.createElement(...) calls, which return plain JavaScript objects describing the UI.
Why must a component's return contain a single root element?
- Because the return is one expression that compiles to one element; group siblings in a fragment
- Because React only renders the first element it finds
- Because multiple roots make the DOM slower
- Because className can only be applied to one node
Answer: Because the return is one expression that compiles to one element; group siblings in a fragment. A return is a single expression compiling to one element. Wrap multiple siblings in a fragment <>...</> to satisfy this.
How do props behave in React?
- They are mutable and can be reassigned by the child
- They flow up from child to parent automatically
- They are global and shared across all components
- They are read-only inputs that flow down from parent to child
Answer: They are read-only inputs that flow down from parent to child. Props are read-only. Data flows down via props; events flow back up via callback props.
A child needs to tell its parent that a value changed. What is the idiomatic way?
- Mutate the prop directly inside the child
- The parent passes a callback prop and the child calls it
- Use a global variable both can read
- The child re-renders the parent manually
Answer: The parent passes a callback prop and the child calls it. Data flows down, events flow up: the parent passes a callback prop, and the child invokes it to report the change.
What is a fragment used for?
- To group sibling elements while adding zero extra DOM nodes
- To add an extra wrapper div to the DOM
- To memoize a component
- To pass props to many children at once
Answer: To group sibling elements while adding zero extra DOM nodes. A fragment <>...</> groups siblings into one root without rendering any additional DOM node.
Does every re-render result in a DOM update?
- Yes, every render rewrites the entire DOM
- Only when state is an object
- No; React diffs the new tree and commits only the changes
- Only in production builds
Answer: No; React diffs the new tree and commits only the changes. React builds a virtual DOM, reconciles it against the previous tree, and commits only the differences. An unchanged tree leaves the DOM alone.
You call items.push(x) then setState, but nothing updates. Why?
- push is asynchronous
- The array reference is unchanged, so React bails out of the update
- setState only works with numbers
- You must call render() manually after push
Answer: The array reference is unchanged, so React bails out of the update. Mutating in place keeps the same reference. React compares by reference, so it skips the update. Pass a new array like [...items, x].
When does useLayoutEffect run relative to the browser paint?
- After paint, like useEffect
- Only on unmount
- It never runs in development
- Synchronously before paint
Answer: Synchronously before paint. useLayoutEffect runs synchronously before paint (useEffect runs after). Use it only for measure-and-adjust layout work.
Why does an effect sometimes run twice in development?
- Because of a bug in React
- Because Strict Mode double-invokes effects to surface missing cleanup
- Because the dependency array is empty
- Because useEffect always runs twice in every environment
Answer: Because Strict Mode double-invokes effects to surface missing cleanup. Strict Mode intentionally mounts, unmounts, and remounts in development (run, cleanup, run) to reveal effects that are missing cleanup. It is dev-only.
How do you apply an inline style object in JSX?
- style='color: red' as a plain string
- css={{ color: 'red' }}
- style={{ color: 'red' }} with a camelCased object
- inline-style='color: red'
Answer: style={{ color: 'red' }} with a camelCased object. Inline styles take an object with camelCased properties, e.g. style={{ backgroundColor: 'blue' }}. Use className for CSS classes.
Continue this course
- Previous: useLayoutEffect vs useEffect
- Next: useRef — DOM Refs & Persisting Values — Reference DOM nodes and persist values without causing re-renders
- Quick reference: React cheat sheet
Frequently asked questions
What does this checkpoint cover?
Everything from the React Fundamentals run: JSX, props and one-way data flow, fragments, how rendering works, styling, Strict Mode, useLayoutEffect vs useEffect, and immutable state updates. The build challenge ties them together.
Do I need to memorize every API?
No. Focus on the ideas: render is pure, data flows down, state updates immutably, and effects clean up. If you can reason about those, the exact syntax is easy to look up.
What if I get stuck on the build challenge?
Try the starter first, run it, and read the errors. Then open Show solution to compare your approach. Understanding why the solution works matters more than matching it line for line.
Am I ready for the next lessons?
If you can pass the quiz and explain your build-challenge code, yes. The next track moves into hooks like useId and beyond, which build directly on these fundamentals.