All lessons
FrontendIntermediate14 min readIn my stack

React

Build interfaces from components: state, hooks, rendering and React 19's server features.

  • React 19
  • Hooks
  • State
  • Server Components
  • Zustand
Step 1/5
A React app is a tree of components: App on top, Header and Cart below, each with children of their own.

In 30 seconds

React is a JavaScript library for building user interfaces from small, reusable pieces called components. You describe what the screen should look like for a given state, and React updates the page efficiently when that state changes. React 19 adds Actions for forms, the use hook, and Server Components that render on the server and send less JavaScript to the browser.

Why I use it

  • I use React for nearly every interface I build: Next.js storefronts, admin panels, an Electron till and, through React Native, mobile apps.
  • I like that the component model scales: the same patterns work for a single button and for an admin system with dozens of screens.
  • I maintain reusable React packages, including a config-driven admin system and account and cart modules, that are shared across many client sites.
  • I choose state tools deliberately: local state first, Zustand for client state, and a server-cache library like TanStack Query for API data.

How it works

01Components, props and state

A component is a function that returns JSX, a syntax that looks like HTML and describes UI. Components compose like building blocks: a ProductCard inside a ProductGrid inside a ShopPage. Props are the inputs a parent passes to a child, read-only from the child's point of view, like function arguments. State is data a component owns and can change, such as whether a menu is open or what is typed in a field. When state changes, React re-renders that component and its children with the new values. The core idea is 'UI is a function of state': you never manipulate the DOM by hand, you change state and describe the result. Data flows down through props; changes flow up through callback props such as onChange. When two components need the same state, lift it up to their closest common parent.

tsx
type Product = { id: string; name: string; price: number };
function ProductCard({ product, onAdd }: { product: Product; onAdd: (id: string) => void }) {
  return (
    <article>
      <h3>{product.name}</h3>
      <p>{(product.price / 100).toFixed(2)} USD</p>
      <button onClick={() => onAdd(product.id)}>Add to cart</button>
    </article>
  );
}
function ProductGrid({ products }: { products: Product[] }) {
  const [cart, setCart] = useState<string[]>([]);
  const add = (id: string) => setCart((prev) => [...prev, id]); // never mutate
  return (
    <section>
      <p>{cart.length} items in cart</p>
      {products.map((p) => <ProductCard key={p.id} product={p} onAdd={add} />)}
    </section>
  );
}

02Hooks: useState, useEffect, useMemo, useRef

Hooks are functions that give components memory and side effects. Call them at the top level of a component, never inside conditions or loops, because React tracks them by call order. useState stores a value and a setter; use the updater form (setCount(c => c + 1)) when the new value depends on the old one. useEffect synchronises a component with something outside React, such as a subscription, a timer or a browser API; return a cleanup function, and list every value it uses in the dependency array. Many effects are unnecessary: if you can calculate a value during render, do that instead of syncing it into state. useMemo caches an expensive calculation between renders, and useCallback caches a function. useRef holds a mutable value that does not trigger re-renders, most often a reference to a DOM node. React 19.2 adds useEffectEvent for reading the latest values inside an effect without re-running it.

tsx
function SearchBox({ items }: { items: string[] }) {
  const [query, setQuery] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);
  // Derived during render: no extra state, no effect
  const results = useMemo(
    () => items.filter((i) => i.toLowerCase().includes(query.toLowerCase())),
    [items, query]
  );
  // Side effect with cleanup: keyboard shortcut "/" focuses the input
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "/") { e.preventDefault(); inputRef.current?.focus(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);
 
  return (<><input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} />
    <ul>{results.map((r) => <li key={r}>{r}</li>)}</ul></>);
}

03Rendering and reconciliation

Rendering in React means calling your component functions to get a description of the UI. It does not mean touching the DOM. React then compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM, in the commit phase. A component re-renders when its state changes, when its parent re-renders, or when a context it reads changes. Re-renders are usually cheap; slow ones come from expensive calculations or very large trees. Keys tell React which list item is which: use a stable id, not the array index, or React may reuse the wrong component and mix up its state. If the element type at a position changes, React throws away the old subtree and its state. Rendering must be pure: no side effects, no mutation of props or state. The React Compiler (stable since late 2025) can memoise components automatically, reducing the need for manual useMemo and memo.

04Choosing where state lives

Most state bugs come from putting state in the wrong place. Think in kinds of state. Local UI state (an open dropdown, a form field) belongs in useState inside the component. State shared by a subtree, or values that rarely change such as theme, locale or the current user, fit Context, but avoid putting frequently changing values in one big context, because every consumer re-renders. Global client state such as a cart, filters or a multi-step wizard is simpler in a small store like Zustand, where components subscribe only to the slice they use. Server state, meaning data that lives in your API, is different: it needs caching, loading and error states, refetching and invalidation. Use TanStack Query or SWR for that in client components, or fetch in Server Components. URL state (search, page, filters) belongs in the URL so it can be shared and bookmarked.

tsx
// Zustand: client state, components subscribe to slices
import { create } from "zustand";
type CartState = { items: string[]; add: (id: string) => void };
export const useCart = create<CartState>((set) => ({
  items: [],
  add: (id) => set((s) => ({ items: [...s.items, id] })),
}));
 
// TanStack Query: server state with caching and refetching
import { useQuery } from "@tanstack/react-query";
function Orders() {
  const count = useCart((s) => s.items.length); // re-renders only on count change
  const { data, isPending, error } = useQuery({
    queryKey: ["orders"],
    queryFn: () => fetch("/api/orders").then((r) => r.json()),
  });
  if (isPending) return <p>Loading...</p>;
  if (error) return <p>Could not load orders.</p>;
  return <p>{data.length} orders, {count} items in cart</p>;
}

05React 19: Actions, useActionState and use

React 19 (released December 2024, with 19.1 and 19.2 following) made async UI much simpler. An Action is an async function used in a transition, most visibly as a form's action prop: React tracks the pending state, handles errors and resets the form after success. useActionState wraps an action and gives you its latest result and an isPending flag, which is ideal for form validation messages. useFormStatus lets a submit button inside the form know it is pending, and useOptimistic shows the expected result immediately while the request runs. The new use API reads a promise or a context during render; with a promise, the component suspends until it resolves and the nearest Suspense boundary shows a fallback. Other quality-of-life changes: ref is now a normal prop (no forwardRef), and <title> and <meta> tags can be rendered from any component. React 19.2 added <Activity> to hide parts of the UI while keeping their state.

tsx
"use client";
import { useActionState } from "react";
type State = { error?: string; ok?: boolean };
async function subscribe(_prev: State, formData: FormData): Promise<State> {
  const email = String(formData.get("email") ?? "");
  if (!email.includes("@")) return { error: "Please enter a valid email." };
  const res = await fetch("/api/subscribe", { method: "POST", body: formData });
  return res.ok ? { ok: true } : { error: "Something went wrong." };
}
export function SubscribeForm() {
  const [state, action, isPending] = useActionState(subscribe, {});
  return (
    <form action={action}>
      <label htmlFor="email">Email</label> <input id="email" name="email" type="email" required />
      <button disabled={isPending}>{isPending ? "Sending..." : "Subscribe"}</button>
      {state.error && <p role="alert">{state.error}</p>}
      {state.ok && <p role="status">Thanks for subscribing.</p>}
    </form>
  );
}

06Server Components

React Server Components (RSC) are components that run only on the server, either at build time or per request. They can be async, read from a database or API directly, and use secrets safely, and their code is never sent to the browser. Only the rendered result is streamed to the client. Client Components, marked with "use client" at the top of a file, are the familiar kind: they ship JavaScript, hydrate in the browser and can use state, effects and event handlers. The mental model is a tree where the outer parts are server-rendered and interactive islands are client components. Server components can pass serialisable props to client components and even render them as children. Server Functions, marked "use server", let client code call server-side code like a function, typically for form submissions. RSC needs a framework that supports it, most commonly the Next.js App Router.

tsx
// app/products/page.tsx  (Server Component: no "use client")
import { AddToCartButton } from "./add-to-cart-button";
 
export default async function ProductsPage() {
  const res = await fetch("https://api.example.com/products");
  const products: { id: string; name: string }[] = await res.json();
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>
          {p.name} <AddToCartButton id={p.id} />
        </li>
      ))}
    </ul>
  );
}
 
// add-to-cart-button.tsx starts with "use client" and uses useState/onClick

Use it when

  • Interactive interfaces with lots of state: dashboards, admin panels, shops, editors.
  • Teams that want one component model across web (React, Next.js), desktop (Electron) and mobile (React Native).
  • Design systems and reusable UI packages shared between several products.
  • Content sites that need some interactivity, when paired with a server-rendering framework like Next.js.

Skip it when

  • A mostly static page or simple brochure site with one or two interactions, where plain HTML, CSS and a little JavaScript are lighter.
  • Client-only React for pages that must rank in search engines and load fast on slow phones; use server rendering (Next.js) instead.

Alternatives

  • Vue (with Nuxt)

    Template-based components with built-in reactivity; gentle learning curve and a strong official ecosystem.

  • Svelte (with SvelteKit)

    A compiler that produces very small bundles and needs less boilerplate for state.

  • Angular

    A full, opinionated framework with dependency injection and signals, popular in large enterprises.

  • Solid, Preact

    React-like APIs with fine-grained reactivity or a tiny size, for performance-critical widgets.

Best practices

  1. Keep components small and focused; lift state only as high as it needs to go.
  2. Derive values during render instead of syncing them into state with effects.
  3. Use stable ids as keys, never the array index for lists that can change.
  4. Separate server state (TanStack Query, Server Components) from client state (useState, Zustand).
  5. Default to Server Components in frameworks that support them; add "use client" only where interactivity is needed.
  6. Measure with the React DevTools Profiler before optimising; consider the React Compiler before adding memo everywhere.
  7. Never store sensitive data, such as raw card details, in persisted client stores.

Common mistakes

  • Using useEffect to copy props into state or to compute derived values, causing extra renders and stale data.
  • Missing or wrong dependencies in useEffect, leading to stale values or infinite loops.
  • Mutating state objects or arrays directly, so React does not see the change.
  • Putting fast-changing values into a large Context that half the app consumes.
  • Fetching data in effects without handling loading, errors, race conditions and caching.

Deep dive

07Fiber, lanes and concurrent rendering

Internally, React represents the component tree as a linked structure of fiber nodes, one per component or element, each holding its props, state, hooks list and pending work. Because rendering is split into small units of work on these fibers, React can pause a render, handle something more urgent and resume, or throw unfinished work away. Every update is assigned a priority lane: a keystroke or click is urgent, while an update wrapped in startTransition is a transition that may be interrupted. That is the foundation of concurrent features. useTransition marks a state update as non-urgent and gives you an isPending flag; in React 19 the callback can be async. useDeferredValue gives you a lagging copy of a value, so an expensive list renders with the old value while the input stays responsive. The commit phase, where React applies changes to the DOM, is never interrupted, so users never see a half-updated screen. This is also why render functions must be pure: React may call them more than once, or not commit their result at all.

tsx
import { useDeferredValue, useState, useTransition, memo } from "react";
 
function ProductSearch({ products }: { products: { id: string; name: string }[] }) {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query); // lags behind while busy
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState<"all" | "sale">("all");
 
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={() => startTransition(() => setTab("sale"))}>Sale</button>
      {isPending && <span>Loading...</span>}
      <Results products={products} query={deferredQuery} tab={tab} />
    </>
  );
}
const Results = memo(function Results(props: { products: { id: string; name: string }[]; query: string; tab: string }) {
  return <ul>{props.products.filter((p) => p.name.includes(props.query)).map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
});

08Hydration, mismatches and Suspense boundaries

When a page is server-rendered, the browser first receives plain HTML. Hydration is React attaching event handlers and state to that existing HTML by rendering the same tree on the client and matching it node by node. If the client render produces different output, for example because it used Date.now(), Math.random(), window size, the user's locale or localStorage, React reports a hydration mismatch and may re-render that part from scratch, losing the benefit of server rendering. Fix it by producing identical output on both sides: pass the value from the server, or render the client-only part after mount, and use suppressHydrationWarning only for unavoidable single-text differences such as a timestamp. Suspense boundaries define where loading states appear, and with streaming SSR they also define hydration units: each boundary can stream its HTML and hydrate independently, and React prioritises hydrating the part the user is interacting with. Place boundaries around independent, slow sections, not around every component, or the page flickers with spinners.

tsx
import { Suspense, useSyncExternalStore } from "react";
// Same output on server and first client render, then real value
function useIsClient() {
  return useSyncExternalStore(
    () => () => {},
    () => true,   // client snapshot
    () => false,  // server snapshot
  );
}
function LocalTime({ iso }: { iso: string }) {
  const isClient = useIsClient();
  return <time dateTime={iso}>{isClient ? new Date(iso).toLocaleString() : iso.slice(0, 10)}</time>;
}
function Page() {
  return <main><ProductDetails /><Suspense fallback={<p>Loading reviews...</p>}><Reviews /></Suspense></main>;
}

09The React Compiler and the rules it relies on

The React Compiler (1.0 since October 2025) is a build-time tool that analyses your components and hooks and inserts memoisation automatically, at a finer level than most people would write by hand: individual JSX elements and values, not just whole components. It can only do that safely if your code follows the Rules of React: components and hooks are pure during render, props and state are treated as immutable, hooks are called unconditionally at the top level, and values passed to hooks are not mutated afterwards. Code that breaks these rules is skipped rather than miscompiled, so the app still works, but those components gain nothing. The recommended rules in eslint-plugin-react-hooks report the same violations, so turn them on before enabling the compiler. Adoption is usually incremental: enable it for one directory, measure with the Profiler, then widen. Once it is on, most hand-written useMemo, useCallback and memo calls become unnecessary, though existing ones are still respected.

tsx
// Breaks the rules: mutating during render, so the compiler skips this component
function CartTotal({ items }: { items: { price: number; qty: number }[] }) {
  items.sort((a, b) => b.price - a.price); // mutates a prop!
  return <p>{items.reduce((sum, i) => sum + i.price * i.qty, 0)}</p>;
}
 
// Follows the rules: derive a new array, no manual memo needed with the compiler
function CartTotalFixed({ items }: { items: { price: number; qty: number }[] }) {
  const sorted = items.toSorted((a, b) => b.price - a.price);
  const total = sorted.reduce((sum, i) => sum + i.price * i.qty, 0);
  return <p>{total}</p>;
}
 
// next.config.ts:  { reactCompiler: true }
// Vite: babel-plugin-react-compiler in the React plugin's babel options

10Rendering performance at scale: lists, context and code splitting

Most React performance problems fall into a few patterns. Very long lists: rendering 5,000 rows creates 5,000 DOM nodes no matter how fast React is, so virtualise with a library like TanStack Virtual, which renders only visible rows, or paginate. Context fan-out: one context holding a frequently changing object re-renders every consumer; split it into separate contexts for state and actions, or move fast-changing data to a store with selectors. State placed too high: typing in a field at the top of the page re-renders everything below; move the state down into the component that owns it, or pass expensive subtrees as children so they are not re-rendered. Large bundles: split rarely used screens with lazy() and Suspense, and import heavy libraries such as charts or editors only where needed. Always confirm with the React DevTools Profiler, enabling 'Highlight updates when components render', and measure on a throttled mid-range phone, not a developer laptop.

tsx
import { lazy, Suspense, useState } from "react";
 
// Code-split a heavy screen: loaded only when first rendered
const ReportsChart = lazy(() => import("./reports-chart"));
 
export function Dashboard() {
  const [showReports, setShowReports] = useState(false);
  return (
    <section>
      <button onClick={() => setShowReports(true)}>Show reports</button>
      {showReports && (
        <Suspense fallback={<p>Loading chart...</p>}>
          <ReportsChart />
        </Suspense>
      )}
    </section>
  );
}

Real-world scenarios

01

Typing in a filter box freezes the product list

Problem

An admin page shows 3,000 products with a search field above them. Each keystroke takes 300 ms to appear on a mid-range laptop, and staff complain that the input lags behind their typing.

How to solve it

Profile first with the React DevTools Profiler to confirm the list is the cost. Then separate urgent from non-urgent work: keep the input bound to state directly, and pass useDeferredValue(query) to the list, wrapped in memo or compiled by the React Compiler, so typing stays instant and the list catches up. Virtualise the list with TanStack Virtual so only about 30 rows exist in the DOM. Precompute a lowercase search field once, not per keystroke. If the data grows further, move filtering to the server with a debounced request and cancel outdated ones. Measure again: the input should respond within one frame.

02

Hydration errors after adding prices in the user's currency

Problem

After formatting prices and dates with the browser's locale, the console fills with hydration mismatch warnings, and some product cards flash and re-render on load.

How to solve it

The server renders with its own locale and time zone, the browser with the user's, so the HTML differs. Decide the locale and currency on the server, from the URL, a cookie or the Accept-Language header, and pass them down so both sides format identically with Intl.NumberFormat. For values that truly depend on the device, such as local time, render a stable placeholder on the server and the precise value after mount, using a useSyncExternalStore client check or an effect. Use suppressHydrationWarning only on a single text node for an unavoidable timestamp. Add a test that renders on the server and hydrates in jsdom, failing on mismatch warnings.

03

A shared React package causes bugs in ten client sites

Problem

An internal UI package is used by many sites. A minor release changed a component's default behaviour and broke checkout on several of them, and nobody noticed until customers complained.

How to solve it

Treat the package like a public library. Follow semantic versioning strictly: behaviour changes are majors, with a changelog and migration notes. Keep React as a peer dependency so sites never load two copies. Add visual regression and interaction tests for every exported component, plus a contract test that renders the components with each consuming site's typical props. Publish pre-release versions and test them in two or three representative sites before the stable release. Use feature flags or new props for behaviour changes rather than changing defaults. Finally, record which sites use which version, so a fix can be rolled out in a controlled order.

Cheat sheet

Mark an update as non-urgent
const [isPending, startTransition] = useTransition();
Keep input fast, let a heavy list lag
const deferred = useDeferredValue(query);
Code-split a component
const Chart = lazy(() => import("./chart"));
Reset a component's state when an id changes
<Profile key={userId} userId={userId} />
Subscribe to a browser API safely
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
Read latest values in an effect without re-running it
const onTick = useEffectEvent(() => log(count));
Optimistic UI during a mutation
const [optimistic, addOptimistic] = useOptimistic(items, reducer);
Enable the React Compiler in Next.js
// next.config.ts { reactCompiler: true }
Lint the Rules of React
npm i -D eslint-plugin-react-hooks // use its recommended config
Find slow renders
React DevTools > Profiler > record > sort by render duration

In my real projects

  • I am a main contributor to an in-house React package ecosystem (around 1,000 of my commits) with a config-driven admin system, a design system of about 20 components, and account, cart and form packages used across around 10 client sites.
  • In that ecosystem I memoised providers, made a loading counter safe for server rendering and kept raw card data out of the persisted cart store, verifying the server-rendered output stayed identical.
  • The multi-tenant POS SaaS uses React 19 with Vite, TanStack Query for server state and Zustand for client state.
  • My five React Native apps share one template; in the coffee-ordering app, screens read only from stores, which keeps data logic out of the UI.

Questions & answers

Q1What is the difference between state and props?

Props are inputs passed from a parent to a child component. The child reads them but must not change them, much like function arguments. State is data that a component owns and can update over time, such as form input, an open or closed menu, or a selected tab. When state changes, React re-renders the component; when a parent re-renders with new props, the child re-renders too. A good rule: if a value can be computed from props or other state, it should not be state at all. If two siblings need the same state, move it to their common parent and pass it down as props.

Q2Why do keys matter when rendering lists?

Keys let React match list items between renders during reconciliation. With stable keys, usually database ids, React knows that the item with id 42 moved from position three to position one and can keep its DOM node and internal state. With array indexes as keys, inserting or removing an item shifts every index, so React may reuse the wrong component: an input keeps text that belongs to another row, or animations and focus jump around. Keys must be unique among siblings and stable across renders; never generate them randomly during render, because that forces React to recreate every item each time.

Q3When should you use useEffect, and when not?

useEffect is for synchronising with systems outside React: subscriptions, timers, WebSockets, browser APIs, third-party widgets, or analytics. It runs after the commit, and its cleanup runs before the next effect and on unmount. You usually do not need it for derived data (compute it during render, with useMemo if expensive), for handling user events (put the logic in the event handler), or for resetting state when a prop changes (use a key). Data fetching inside effects works but needs race-condition handling and caching, so I prefer TanStack Query, SWR or Server Components. Fewer effects means fewer bugs.

Q4How do you decide between Context, Zustand and TanStack Query?

By the kind of state. TanStack Query (or SWR) is for server state: data owned by the API that needs caching, background refetching, deduplication and invalidation after mutations. Zustand is for global client state that changes often, such as a cart, filters or a multi-step flow; components subscribe to slices, so only what changed re-renders. Context is best for dependency injection and rarely changing values like theme, locale or the current user; it re-renders all consumers on every change, so it is a poor store for fast-changing data. Local useState is still the default for anything only one component needs.

Q5What are React Server Components and how are they different from SSR?

Server-side rendering (SSR) renders client components to HTML on the server for a fast first paint, but all their JavaScript is still sent and hydrated in the browser. Server Components are a different kind of component: they run only on the server, can be async and access databases or secrets directly, and their code never ships to the client. Only their rendered output is streamed down, and interactive parts are Client Components marked "use client". The two work together: a Next.js page renders Server Components, server-renders the Client Components to HTML, then hydrates only the client parts. The result is less JavaScript and simpler data fetching.

Q6What changed in React 19 that matters day to day?

The biggest change is Actions: async functions that React runs in a transition, typically through a form's action prop. Together with useActionState, useFormStatus and useOptimistic, they remove most hand-written loading, error and optimistic-update code in forms. The use API reads promises and context during render, working with Suspense. ref is now an ordinary prop, so forwardRef is no longer needed, and components can render <title> and <meta> tags directly. Server Components and Server Functions are stable for frameworks. React 19.2 added <Activity> and useEffectEvent, and the React Compiler reached 1.0, automating much of the memoisation people used to write by hand.

Senior-level questions

S1How would you architect state management for a large React application with many teams?

I classify state first, because most problems come from mixing kinds. Server state goes through one data layer, TanStack Query or Server Components, with agreed query-key conventions and invalidation rules, so caching is consistent and nobody copies API data into stores. URL state, filters, pagination and tabs, lives in the router so it is shareable. Global client state is kept small, split into feature-owned Zustand stores with selectors, and each store has a clear owner team. Everything else stays local. Cross-feature communication goes through explicit interfaces, events or shared hooks, not by reaching into another team's store. The trade-off is more structure and some duplication between features, accepted in exchange for teams being able to change their area without breaking others.

S2What problems do Server Components solve, and what do they make harder?

They solve three real problems. Bundle size: code that only formats data, renders markdown or calls a database stays on the server, so browsers download and parse less JavaScript. Data fetching: components can await data directly, close to where it is used, without client waterfalls or an API layer built just for the UI. Security: secrets and internal services never reach the client. What gets harder is the mental model: developers must know which side every file runs on, what can cross the boundary (only serialisable props), and how caching and revalidation work in their framework. Debugging spans two environments, and third-party libraries that use context or effects need client wrappers. They also tie you to a framework that implements them. For highly interactive apps behind a login, a client-side SPA can still be the simpler choice.

S3useEffect data fetching, TanStack Query, or Server Components: how do you choose?

Raw fetching in useEffect is the weakest default: you have to handle race conditions, cancellation, caching, deduplication, retries and refetching yourself, and it creates request waterfalls, so I use it only for trivial, isolated cases. TanStack Query is the right tool for client-rendered apps and highly interactive data: dashboards, admin tables, infinite lists, optimistic mutations and background refresh after the tab regains focus. Server Components fit data needed to render the initial page, especially public, SEO-relevant or secret-dependent data, because it arrives with the HTML and adds no client JavaScript. In a Next.js app I commonly combine them: Server Components for the first render and Server Actions for mutations, with TanStack Query reserved for parts that need live client-side updates, polling or complex caching.

Glossary

Fiber
React's internal unit of work: one node per component or element, holding props, state and pending updates.
Reconciliation
Comparing the new element tree with the previous one to decide the minimal DOM changes.
Commit phase
The uninterruptible step where React applies calculated changes to the DOM and runs layout effects.
Transition
A non-urgent state update that React may interrupt to keep urgent interactions, like typing, responsive.
Hydration
Attaching React's state and event handlers to HTML that was rendered on the server.
Hydration mismatch
The client's first render differs from the server HTML, forcing React to re-render that part.
Suspense boundary
A component that shows a fallback while its children wait for code or data, and streams independently.
Virtualisation
Rendering only the list rows currently visible on screen, instead of every row in the data.
Rules of React
Purity, immutability and hook-ordering rules that make React's optimisations and the compiler safe.
Peer dependency
A package a library expects the host app to provide, such as React, so only one copy is loaded.

Have a product in mind?Let's build it.

Tell me about your idea, your product or your team. I usually reply within one working day.