A 36-minute working session on how React actually thinks — components and one-way data flow, the hooks you reach for daily, the effects you usually don't need, and where your code runs now that the server is back in the picture.
React's big idea is small: describe what the screen should look like for the current data, and let React work out the DOM changes. You never reach into the page and edit nodes by hand — you change state and re-describe. Three concepts carry the whole model: components, JSX, and one-way data flow.
State goes in, a description of the UI comes out. An event sets new state and the loop runs again.
One-way flow: data goes down as props; events come up as callbacks. No child reaches sideways into a sibling.
It looks like HTML but it's an expression: {} drops any JS value in, className replaces class, and every tag must close.
A parent passes data down. A component nevermutates its props — they're the arguments to its render function.
Build screens by nesting small components. The children prop lets a component wrap whatever you put inside it.
If a value is handed in by a parent, it's a prop. If a component needs to remember something across renders — a count, a form field, whether a menu is open — that's state, and you reach for a hook. Hooks are the functions starting with usethat let a plain function component hold state and tap into React's features.
The setter is the only door: it tells React the value changed and a re-render is due.
useState vs useReducerreducer(state, action) function. The component just dispatches actions.Like numbered lockers — React hands out state by the order you ask for it, so you must ask in the same order every time. Typing props and hook return values is its own skill; see the TypeScript deck.
useEffectis not "run some code after render" — it's a way to keep React in sync with an external system: a network connection, a browser API, a non-React widget, a subscription. If the work is purely about turning props and state into JSX, it belongs in render, not in an effect.
Setup connects to the outside system; the returned cleanup disconnects. Every effect that subscribes must unsubscribe.
The dependency array is a promise to React: "these are the only values this effect depends on." Keep it honest.
key instead.localStorage.A missing cleanup leaks listeners; a dishonest dependency array causes stale closures. In dev, Strict Mode runs effects twice on mount on purpose — to surface a missing cleanup.
When a value is needed deep in the tree — the current theme, the logged-in user, a locale — passing it down through every intermediate component is prop drilling: tedious, and every middle component is now coupled to data it doesn't use. Two tools fix this: better composition, and Context.
Left: the value is handed down link by link. Right: the Provider publishes once and the deep child reads it directly.
Much "drilling" disappears if you pass JSX as childreninstead of passing data through. A layout component doesn't need to know what it wraps.
Like a building's PA system — the front desk announces once and every floor hears it, instead of passing a note desk to desk. Use it for announcements, not for chatter.
React re-renders a component when its state changes or its parent re-renders. That's usually fast and fine — re-rendering computes JSX, it doesn't touch the DOM unless something actually changed. Most performance work is about two things: stable list keys, and not re-doing expensive work needlessly. Reach for memoonly after you've measured.
A parent re-render flows to every child. memo lets a child skip re-rendering when its props are unchanged.
memo'd child doesn't see a "new" prop every render.All three trade memory and complexity for fewer renders. Used everywhere by reflex, they make code harder to read and slower to write — for no measured gain.
useMemo / useCallbacksimply isn't needed — you write plain code and the compiler inserts the caching. Treat manual memoization as the exception, not the habit.A keyis React's identity tag for a list item. Use a stable id from your data — never the array index for a list that can reorder, insert, or delete.
Modern React splits components by where they run. A Server Component runs only on the server, can fetch data directly, and sends zero JavaScript to the browser. A Client Component is the interactive part — state, effects, event handlers — and is the only kind that hydrates on the device.
async and awaitdata inline, but it can't use state, effects, or browser APIs. Mark the interactive boundary with "use client".Server renders and streams; only the "use client" islands ship JS and hydrate.
<Suspense fallback=...> and React shows the fallback until the content is ready — then streams it in.isLoading flags threaded through your tree; the boundary owns the loading state.use() hook lets a component read a promise (or context) and suspend until it resolves.Where the HTML is ultimately built — server, client, or statically — is the subject of Rendering Strategies.
Server Components need a framework to render and route them. Pick on how much server you actually want.
Pro — most complete RSC + App Router story; huge ecosystem and deploy options.
Con — opinionated and large; the caching model has a learning curve.
Choose when you want server rendering and data fetching handled for you, end to end.
Pro — the Remix lineage; explicit loaders/actions, works as a framework or a plain router.
Con — smaller RSC surface; fewer batteries than Next.
Choose when you want web-standards data flow and room to start as an SPA and grow.
Pro — simplest model: a pure client-rendered app, fast dev server, no server runtime.
Con — no Server Components; you own SEO, data fetching, and code-splitting.
Choosefor internal tools and dashboards behind a login, where SEO and first-paint don't rule.
The honest default: an internal app is happiest as a Vite SPA; a public, content-heavy productwants a server framework. Don't adopt RSC for an app that never needed a server.
React ships the primitives — elements, state, effects. It has no built-in buttons, modals, or design system. That gap is where component libraries live.
Pro — unstyled, accessible behavior primitives (menus, dialogs) you style yourself.
Con — you bring all the visuals; more upfront work.
Choose when you need a bespoke design system but not to reinvent accessibility.
Pro — copy styled components (built on Radix) into your repo; you own and edit the code.
Con— it's a starting point, not a versioned dependency; you maintain it.
Choose when you want a head start and full control of the source.
Pro — batteries-included design system; ship a polished app fast.
Con— heavier bundle; harder to escape the library's look.
Choose for internal tools or when speed beats a custom brand.
Primitives end where design opinions begin: a primitive gives you correct, accessible behavior; a UI library adds the visual system on top. State tooling is a separate axis — see State Management.
Where you fetch follows where you render. On the server, a component just awaits its data. On the client, you don't roll your own — you reach for a server-state library that handles caching, refetching, and staleness for you.
Fetch in a Server Component and pass data down. No loading flags, no client waterfall — the data arrives with the HTML.
For data fetched in the browser, use TanStack Query or SWR: caching, dedupe, and background refetch. Don't store server data in useState.
Actions and useActionState handle form submissions and pending/optimistic UI without a hand-rolled fetch-and-setState dance.
The split between server state (data you cache from a backend) and client state (UI-only) is the heart of the State Managementdeck — most "React is hard" pain is really server state in disguise.
memo matters rarely — and the compiler does most of it."use client" islands for interactivity.Five quick questions on the mental model, hooks, effects, performance, and Server Components — instant feedback, no sign-in.
Navigate with ← → or scroll · back to library