Frontend · Guide

React

Components and JSX, props and state, hooks and effects, lists and keys, context, and the boundaries that stop one failure taking the page.

— min read Frontend

The One Idea

Your UI is a function of your state. Change the state and describe what the screen should look like; React works out the DOM operations. Everything else follows from that.

Before React, code mutated the DOM directly — find the node, change the text, toggle a class. That works until two places can change the same thing, at which point the screen and the data quietly disagree.

React inverts it. You never say how to update; you say what the output should be for the current state. Re-rendering is cheap because React compares the described output against the previous one and applies only the differences.

Learn one component model deeply and the rest transfer. Vue, Svelte and Angular solve the same problem with different syntax; the reasoning about state, derived values and effects is the same everywhere.

Components & JSX

A component is a function that takes an object of inputs and returns a description of some UI. JSX is syntax sugar over ordinary function calls — it compiles away entirely.

JSX — and what it compiles to
function Greeting({ name, muted }) {
  return <p className={muted ? 'dim' : ''}>Hello, {name}</p>;
}

// the compiler turns that into a plain function call:
// jsx('p', { className: ..., children: ['Hello, ', name] })

// so it is JavaScript, and normal rules apply:
{items.map(i => <Row key={i.id} {...i} />)}   // loops are map
{isOpen && <Panel />}                        // conditionals are expressions

Because it is JavaScript, there is no template language to learn — but the attribute names follow the DOM properties rather than HTML: className not class, htmlFor not for, and event handlers are camelCase functions rather than strings.

A component name must be capitalised. Lowercase is treated as a literal HTML tag, so <greeting /> renders an unknown element and nothing happens — with no error.

Props & State

Props come from the parent and are read-only. State belongs to the component and, when it changes, causes a re-render. That is the whole distinction, and most design mistakes are putting a value in the wrong one.

State that updates predictably
const [count, setCount] = useState(0);

// wrong: both calls read the same stale count, so this adds 1
setCount(count + 1);
setCount(count + 1);

// right: the updater form sees the value React is about to use
setCount(c => c + 1);
setCount(c => c + 1);   // adds 2

// never mutate — React compares by reference
items.push(x);          // same array, no re-render
setItems([...items, x]);  // new array, renders

State updates are asynchronous and batched: reading the variable straight after setting it gives the old value. That surprises everyone once, and the updater form is the answer whenever the next value depends on the previous one.

Do not put in state anything you can calculate during render. A fullName derived from first and last is not state — it is an expression. Storing it creates a second source of truth that can drift.

When two components need the same value, move it to their nearest common parent and pass it down. That is lifting state up, and it is the answer long before any state library is.

Hooks & Effects

Hooks let a function component hold state and reach outside itself. They must be called unconditionally at the top level — React tracks them by call order, so a hook inside an if shifts every hook after it.

HookFor
useStateA value that changes and should re-render
useEffectSynchronising with something outside React
useRefA mutable box that does not re-render; DOM access
useMemoCaching an expensive calculation
useCallbackKeeping a function's identity stable across renders
useContextReading a value provided further up the tree

useEffect is the most misused. It is for synchronising with an external system — a subscription, a timer, the document title, a network request. It is not a general "run this after render" hook, and reaching for it to compute derived data is the most common source of extra renders and stale values.

An effect with the two parts people forget
useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);   // 1. cleanup
}, [tick]);                            // 2. dependencies

// no dependency array  -> runs after every render
// []                   -> runs once on mount
// [a, b]               -> runs when a or b change
Missing cleanup is how you get duplicate subscriptions, timers that never stop, and state updates on unmounted components. If the effect starts something, the returned function must stop it.

Lists, Keys & Reconciliation

When state changes, React builds a new description and compares it with the last. That comparison is reconciliation, and for lists it needs help identifying which item is which.

That is what key is for. It must be stable, unique and tied to the item — a database id, not an array index.

KeyResult
A stable idCorrect: React moves the existing element
The array indexBreaks on insert, delete or reorder
Math.random()Every item remounts on every render
OmittedFalls back to index, with a warning

The index looks fine until you delete the first row. Every remaining item shifts index, so React thinks each one changed rather than moved — and any internal state, like what someone had typed into an input, lands on the wrong row.

Index keys are safe only when the list is never reordered, filtered, or added to except at the end, and the items hold no state of their own. That is rarer than it sounds.

Context & Composition

Passing a prop through five components that do not use it — prop drilling — is the problem context solves. A provider publishes a value; any descendant reads it without the layers between knowing.

Use it for genuinely global, rarely-changing things: the theme, the signed-in user, the language. It is not a state manager, and a context whose value changes often re-renders every consumer.

Try composition first. Passing children as a prop often removes the drilling entirely — a layout that accepts a <Sidebar> element does not need to know what is inside it, so nothing has to be threaded through.

Custom hooks are the other composition tool. Any function starting with use that calls other hooks is one, and it lets you share stateful logic — a fetch, a subscription, a form — between components without sharing markup.

Error Boundaries & Suspense

By default a thrown error during render unmounts the entire tree — the user gets a blank page. An error boundary catches errors from its children and renders a fallback instead, so one broken widget does not take the application.

Boundaries catch errors during rendering, in lifecycle methods and in constructors below them. They do not catch errors in event handlers, in asynchronous code, or in the boundary itself — those still need ordinary try/catch.

Suspense is the same idea for waiting rather than failing: it renders a fallback while something below it is not ready. Wrap the part of the page that depends on slow data, so the rest still paints.

Place boundaries where a partial failure is acceptable — around a sidebar, a comments panel, a chart. One boundary at the root only converts a blank screen into a slightly friendlier blank screen.

Common Mistakes

MistakeWhat happens
Mutating state directlySame reference, so no re-render
Array index as keyState lands on the wrong row after a reorder
useEffect for derived dataExtra render, stale values, sometimes a loop
Missing effect cleanupDuplicate timers and subscriptions
Reading state right after setting itGets the old value; updates are batched
Hook inside a conditionCall order shifts and hooks break
Everything in contextEvery consumer re-renders on every change

Interview Questions

Why must keys be stable and unique?

They tell React which item is which between renders. With array indexes, deleting or reordering shifts every index, so React treats moves as changes and any internal state — text typed into an input, for instance — ends up on the wrong item.

What is useEffect actually for?

Synchronising with something outside React: a subscription, a timer, the document title, a request. It is not a post-render hook, and using it to compute derived data causes an extra render and stale values.

Why does calling setCount(count + 1) twice only add one?

Both calls close over the same value from this render, and updates are batched. The updater form, setCount(c => c + 1), receives the pending value, so two calls add two.

Props or state?

Props come from the parent and are read-only. State is owned by the component and re-renders it when it changes. If a value can be computed from props or other state, it should not be state at all.

Why can hooks not go inside conditions?

React identifies hooks by call order, not name. A conditional hook changes that order between renders, so state from one hook is handed to another.

When would you reach for context?

Rarely-changing global values — theme, current user, locale. For prop drilling, try composition first; for frequently-changing shared state, context re-renders every consumer and a purpose-built store is better.

Quick Quiz

1. items.push(x) then setItems(items) does not re-render because…
2. The safest key for a list of records is…
3. An effect that starts an interval must return…
4. Hooks must be called…
5. An error boundary does NOT catch errors in…