React and Next.js
How React turns state into UI, when effects are needed and where data should live, how Next.js renders and caches pages on the server, and what React 19 and Server Components changed. Versions as of October 2026: React 19.3, Next.js 16.4.
Before this topic: JavaScript and TypeScript.
How does React work: components, props, state and hooks?
Section titled “How does React work: components, props, state and hooks?”What it is
- Component — a function that returns JSX: HTML-like markup right inside JavaScript.
- Props — inputs from the parent, read-only.
- State — data the component remembers between renders.
How to use
const [count, setCount] = useState(0)— the value and the function that changes it.- Calling
setCount(count + 1)triggers a new render with the new value. - Render lists with
mapand give each item akey— a stable id. - Make inputs controlled:
valuecomes from state,onChangeupdates state.
Common mistake
Mutating state directly: todos.push(x). The array reference stays the same, so React does not see the change. You need a new array: setTodos([...todos, x]).
Takeaway: a component is a function of props and state, and new state via setState means a new render.
How it works
- Render. React calls the component function and gets an element tree.
- Commit. It compares the tree with the previous one and patches the DOM.
- Repeat. New state or a parent re-render runs the component again, and by default all its children.
Rules of hooks
- Only at the top level of a component or custom hook: not in conditions, loops or after an early
return. React finds hook state by call order. - Only from components and from hooks whose name starts with
use. eslint-plugin-react-hookscatches violations.
Pitfalls
- State is a snapshot. After
setCount(count + 1)thecountvariable keeps its old value until the next render. Three such calls in a row give +1, whilesetCount(c => c + 1)gives +3. - Index as
key. Insert at the top or sort, and row input and focus move to the wrong items. - One-way data flow. A child does not change props; it calls a parent callback:
onChange,onSelect.
Rule of thumb
Keep minimal state and derive the rest during render. Put shared state in the closest common parent.
Takeaway: render is a pure function of props and state, and hooks work as long as their call order never changes.
Under the hood
- Every component instance has a fiber node. It stores the list of hooks in call order, so a conditional hook shifts the list and corrupts state.
- Reconciliation is a linear heuristic: an element of a different type is not diffed deeply but recreated with its subtree. List children are matched by
key. - Since React 18, updates are batched everywhere (automatic batching): several
setStatecalls in a handler, promise or timer produce one render.
Cost
- Render is cheaper than commit but not free: re-rendering a large subtree on every keystroke is noticeable. Fast-changing state lives in leaf components.
- Changing
keyor element type resets the state of the whole subtree. That is also a tool:key={userId}resets a form when the user changes.
Trade-offs
- A controlled input gives per-keystroke validation at the cost of a render per character. Uncontrolled inputs with
FormDataare cheaper when you only need the value on submit. - In development, StrictMode calls components twice to catch side effects in render. Concurrent rendering and React Compiler are built on the same purity.
How to check in production
- The React DevTools Profiler with “Record why each component rendered while profiling” shows the cause of every render.
- “Highlight updates when components render” flashes the components that re-rendered.
Takeaway: a component’s identity is its position and key, and a pure render is the condition every React optimization depends on.
import { useState } from 'react';
type Todo = { id: number; title: string };
export function TodoList({ initial }: { initial: Todo[] }) { const [todos, setTodos] = useState(initial); // component state const [title, setTitle] = useState(''); // controlled input
function add() { // A new array, not push: React compares references setTodos([...todos, { id: Date.now(), title }]); setTitle(''); }
return ( <> <input value={title} onChange={(e) => setTitle(e.target.value)} /> <button onClick={add} disabled={!title}>Add</button> <ul> {todos.map((t) => <li key={t.id}>{t.title}</li>)} {/* key is a stable id, not the index */} </ul> </> );}- Why can’t you call a hook inside
if? — React finds hook state by call order, and a condition shifts that order. - What do three
setCount(count + 1)calls in a row give? — +1: they all see the samecountsnapshot; use thec => c + 1updater to accumulate. - Why is the index a bad
key? — On insert or sort, row state ends up attached to the wrong items. - How do you reset a component’s state when the user changes? — Pass
key={userId}: a new key means a new instance. - Does a child re-render if its props did not change? — Yes, by default a parent render renders its children;
memoor React Compiler change that.
How do you use useEffect correctly, and when don’t you need it?
Section titled “How do you use useEffect correctly, and when don’t you need it?”What it is
useEffect(setup, deps) runs setup after React has updated the screen. The function setup returns is the cleanup.
How to use
- Inside — work with the outside world:
addEventListener,setInterval, WebSocket. - In the dependency array — everything the effect reads from props and state.
- Return a cleanup:
removeEventListener,clearInterval, closing the connection.
| Dependencies | When it runs |
|---|---|
| no array | after every render |
[] |
once, after it appears on screen |
[userId] |
on appear and whenever userId changes |
Common mistake
Syncing derived data with an effect: useEffect(() => setFullName(first + ' ' + last), [first, last]). That is an extra render; const fullName = first + ' ' + last right in the component is enough.
Takeaway: an effect is for talking to an external system; calculations happen during render.
How it works
- Render → commit to the DOM → the browser paints → React runs effects.
- On the next render React compares dependencies with
Object.is. If something changed — the previous effect’s cleanup, then the new setup. - On unmount the last cleanup runs.
Pitfalls
- Stale closure. An effect sees the props and state of its own render.
setInterval(() => setCount(count + 1))with[]gets stuck at 1; usesetCount(c => c + 1). - Object or function in dependencies. They are recreated on every render, so the effect always re-runs. Create them inside the effect or depend on primitives.
- Response race. When
querychanges fast, an old response can arrive last. Anignoreflag orAbortControllerin cleanup drops it. - Double run in development. If it breaks your logic, the effect has no proper cleanup.
When you don’t need an effect
| Task | Instead of an effect |
|---|---|
| derived value | compute during render, expensive — useMemo |
| reacting to a click or submit | event handler |
| resetting state when a prop changes | key on the component |
| data fetching | TanStack Query, Server Component |
Rule of thumb
Use an effect when code runs because the component was displayed, not because the user did something.
Takeaway: dependencies are everything you read, cleanup mirrors setup, and the rest goes into render and handlers.
Under the hood
useEffectruns after paint and does not block it.useLayoutEffectruns before paint, for DOM measurement, and delays the frame.- Effects do not run during server rendering — only in the browser.
- In development StrictMode adds a setup → cleanup → setup cycle to every effect. Production does not do this, but it models real scenarios: navigating away and back, Fast Refresh.
Cost
Fetching in an effect starts only after the JS has loaded and the component has rendered. Nested components with their own effects form a waterfall: the child’s request waits for the parent’s response. There is no cache, deduplication or retry on failure.
Trade-offs
- To read the latest value without a re-run, say the theme inside a connection handler, use
useEffectEvent(stable since React 19.2). Disablingexhaustive-depshides a bug. - Subscribing to an external store —
useSyncExternalStore, not an effect plussetState: it is correct under concurrent rendering. - Data — a Server Component or TanStack Query: caching, cancellation and races are solved there.
How to check in production
eslint-plugin-react-hookswithexhaustive-depsandset-state-in-effectas errors in CI.- In the React DevTools Profiler an “effect →
setState→ render” chain shows up as extra back-to-back commits.
Takeaway: an effect is a tool for synchronizing with the outside world; fewer effects mean fewer waterfalls, races and wasted renders.
import { useEffect, useState } from 'react';
type Item = { name: string; active: boolean };
export function Search({ query, onlyActive }: { query: string; onlyActive: boolean }) { const [items, setItems] = useState<Item[]>([]);
// Effect: sync with an external system (the server) when query changes useEffect(() => { let ignore = false; // a response for a stale query won't overwrite the new one fetch(`/api/items?q=${encodeURIComponent(query)}`) .then((r) => r.json()) .then((data: Item[]) => { if (!ignore) setItems(data); }); return () => { ignore = true; }; // cleanup: before a re-run and on unmount }, [query]);
// Not an effect: a derived value is computed right during render const visible = onlyActive ? items.filter((i) => i.active) : items;
return <ul>{visible.map((i) => <li key={i.name}>{i.name}</li>)}</ul>;}import { useEffect, useState } from 'react';
export function Timer() { const [count, setCount] = useState(0);
useEffect(() => { // Bug: setCount(count + 1) sees count = 0 from the first render — stuck at 1 const id = setInterval(() => setCount((c) => c + 1), 1000); // the updater gets the current value return () => clearInterval(id); }, []);
return <p>{count}</p>; // 1, 2, 3...}- How does
useEffectdiffer fromuseLayoutEffect? — A layout effect runs before paint, for DOM measurement; a regular one runs after. - Why does an effect fire twice in development? — StrictMode checks that cleanup undoes setup; production does not re-run it.
- How do you avoid re-running an effect when you only need to read the latest value? — Move the read into
useEffectEvent(React 19.2+). - How do you cancel a request when parameters change? —
AbortControllerwithabort()in cleanup, or anignoreflag. - Can the effect function be
async? — No: it must return a cleanup, not a promise; declare an async function inside and call it.
How do you manage state in a React app?
Section titled “How do you manage state in a React app?”What it is
State is the data the screen depends on: whether a dialog is open, the text in an input, a list of orders.
How to use
- Start with
useStatein the component that needs it. - Two components need it — lift the state up into the closest common parent and pass down the value and a setter.
- The value is needed deep down and in many places (theme, current user) — Context.
- Server data —
useQueryfrom TanStack Query: it loads, caches and refreshes it.
Common mistake
Copying props into state: useState(props.user.name). The initial value is read once, so when props change the state stays stale.
Takeaway: keep state as close as possible to where it is used, and lift it only when several components need it.
How it works
- Lifting state up. State moves to the common parent; children get the value and a callback. The cost — props pass through components that don’t need them.
- Context.
createContext, a<ThemeContext value={theme}>provider (React 19) anduseContextoruse. When the value changes, every consumer re-renders. - Store. Zustand and Redux Toolkit keep state outside the React tree. A component subscribes with a selector and re-renders only when the selected slice changes.
- Server state. TanStack Query caches responses by
queryKey. After a mutation,invalidateQueriesmarks data stale and refetches the active queries.
When to choose what
| State | Where it lives |
|---|---|
| dialog open, input text | useState in the component |
| needed by siblings | common parent |
| theme, locale, user | Context |
| cart, complex shared UI | Zustand or Redux Toolkit |
| API data | TanStack Query, Server Component |
| filters, page, tab | URL (searchParams) |
Pitfalls
- A new object in the provider’s
valueon every render re-renders all consumers. Split contexts or memoizevalue. - A Zustand selector that returns a new object or array re-renders on every store change — wrap it in
useShallow.
Rule of thumb
First decide whose data it is: the server’s, the URL’s or the UI’s. That decides where it lives.
Takeaway: server state lives in the query cache, navigation state in the URL, client state as locally as possible.
Under the hood
- Context compares
valuewithObject.isand re-renders every consumer below the provider;memodoes not stop them. - Zustand is built on
useSyncExternalStore: a component re-renders only if the selector result changed byObject.is. - TanStack Query (v5) treats data as stale immediately by default (
staleTime: 0) and refetches on mount, window focus and reconnect. Unused cache lives for 5 minutes (gcTime).
Cost
- Without
staleTime, every mount and every return to the tab is a network request. Reference data gets minutes. queryKeyis a contract: an imprecise key means a stale screen after a mutation or extra requests.
Trade-offs
- Zustand — minimal code and a hook with no provider. Redux Toolkit — strict structure, action history in DevTools and RTK Query; justified in a large team.
- In the Next.js App Router data is read in Server Components. TanStack Query remains for the interactive client: infinite lists, polling, optimistic updates.
- A module-level store on the Next.js server is shared by all requests, and one user’s data leaks to another. The Zustand docs recommend creating the store per request and passing it through Context.
How to check in production
- TanStack Query Devtools shows keys, freshness and the number of observers per query.
- The React DevTools Profiler shows how many components a Context value change re-rendered.
Takeaway: choosing where state lives means choosing the data’s owner, and speed depends on subscription granularity.
import { create } from 'zustand';import { useShallow } from 'zustand/react/shallow';
type CartState = { items: { id: number; qty: number }[]; add: (id: number) => void;};
export const useCart = create<CartState>()((set) => ({ items: [], add: (id) => set((s) => ({ items: [...s.items, { id, qty: 1 }] })), // a new array}));
export function CartBadge() { // Selector: re-renders only when the item count changes const count = useCart((s) => s.items.length); return <span>{count}</span>;}
export function CartIds() { // map creates a new array on every call — useShallow compares element by element const ids = useCart(useShallow((s) => s.items.map((i) => i.id))); return <p>{ids.join(', ')}</p>;}import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
type Order = { id: number; status: string };
export function Orders() { const queryClient = useQueryClient();
const { data, isPending } = useQuery({ queryKey: ['orders'], // cache key queryFn: (): Promise<Order[]> => fetch('/api/orders').then((r) => r.json()), staleTime: 30_000, // data stays fresh for 30 seconds: no refetches });
const close = useMutation({ mutationFn: (id: number) => fetch(`/api/orders/${id}/close`, { method: 'POST' }), // Invalidation: the cache is stale, active queries refetch onSuccess: () => queryClient.invalidateQueries({ queryKey: ['orders'] }), });
if (isPending) return <p>Loading…</p>; return ( <ul> {data?.map((o) => ( <li key={o.id}> #{o.id} {o.status} <button onClick={() => close.mutate(o.id)}>Close</button> </li> ))} </ul> );}- Why do all Context consumers re-render? — They subscribe to the whole
value: when the reference changes, all update, andmemodoes not help. - How does server state differ from client state? — The server owns it: data goes stale, so it is cached, refetched and invalidated.
- What are
staleTimeandgcTime? — How long data counts as fresh and how long unused cache stays in memory; 0 and 5 minutes by default. - When Redux Toolkit rather than Zustand? — A large team, strict conventions, action history in DevTools, RTK Query.
- Why is a global store dangerous in Next.js? — A module on the server is shared by all requests; create the store per request.
How do Next.js and the App Router work?
Section titled “How do Next.js and the App Router work?”What it is
Next.js adds routing, server rendering, bundling and server-side code to React. The App Router is the router based on the app/ folder.
How to use
app/page.tsxis/,app/blog/[slug]/page.tsxis/blog/hello. Square brackets mark a dynamic segment.layout.tsxwraps every page below it: header, menu.loading.tsxanderror.tsxare the loading and error screens for a segment.- A Server Component can be
asyncand get data withawait. - Need
useStateoronClick— make'use client'the first line of the file.
Common mistake
Putting 'use client' on a whole page for the sake of one button. Then all the page’s code goes to the browser; only the button should be a Client Component.
Takeaway: folders are URLs, components are server ones by default, and 'use client' is only for the interactive parts.
How it works
- A request passes through
proxy.ts(before Next.js 16 —middleware.ts), if present: redirects, rewrites, headers. - Server Components render into the RSC Payload — a serialized tree with slots for Client Components.
- The HTML for the first load is built from it, and the browser loads the Client Components’ JS.
- On navigation via
<Link>the server returns only the segments that changed; shared layouts do not re-render.
When to choose what
| Mode | When the HTML is ready |
|---|---|
| SSG | at build time; the same for everyone |
| ISR | at build time, refreshed in the background by time or tag |
| SSR | per request; user-specific data |
Server-side code
- Server Actions (
'use server') — functions that change data from forms and handlers. They are invoked with a POST request. - Route Handlers (
app/api/.../route.tswithexport async function GET) — an HTTP API for webhooks and external clients, such as a mobile app.
Pitfalls
- Since Next.js 15,
params,searchParams,cookies()andheaders()are async; 16 removed synchronous access. - Since Next.js 15,
fetchand GET Route Handlers are not cached by default. - A Server Action is a public endpoint: check authorization inside every one.
Rule of thumb
Read data in Server Components, change it in Server Actions, and use a Route Handler for external consumers.
Takeaway: the App Router means Server Components by default, nested layouts and explicit server code for reading and changing data.
Under the hood
- With
cacheComponents: trueeverything dynamic runs per request.'use cache'on a function, component or file caches the result; the key is the arguments and captured values, so they must be serializable. - At build time static and cached output goes into the static shell, and request data behind
Suspenseis replaced by its fallback. A CDN serves the shell, and the rest streams in the same response. cookies()andheaders()are not available inside'use cache': read the value outside and pass it as an argument, or use'use cache: private'.
Invalidation
| API | Semantics |
|---|---|
updateTag(tag) |
Server Actions only; the author sees their changes at once |
revalidateTag(tag, 'max') |
stale-while-revalidate: serve old, rebuild in the background |
refresh() |
refresh uncached data, leaves the cache alone |
Trade-offs
- Without Cache Components the previous model applies: a route is static until it reads request data, and
export const revalidatesets up ISR. A singlecookies()read makes the whole page dynamic. - By default the
'use cache'cache lives in the instance’s memory. Shared across instances —'use cache: remote'with a cache handler. proxy.tsruns on Node.js;middleware.tsfor Edge is deprecated. Proxy is for fast redirects and headers, not full authorization.- Deployment: Vercel,
next startoroutput: 'standalone'in Docker, platform adapters (stable since 16.2).output: 'export'is static files with no server features.
How to check in production
next buildprints the type of every route, and the dev overlay flags request-data access outsideSuspense.export const ensureStatic = 'navigation'(16.4) fails the build if a dynamic component sneaks into a static route.
Takeaway: in Next.js 16 caching is explicit and granular: a static shell from the CDN, dynamic parts streamed, invalidation by tag.
// app/products/[id]/page.tsx — a Server Component by defaultimport { Suspense } from 'react';import { cacheLife, cacheTag } from 'next/cache';import { AddToCart } from './add-to-cart'; // a file with 'use client'
type Props = { params: Promise<{ id: string }> }; // params is a promise since Next.js 15
export default function Page({ params }: Props) { return ( <Suspense fallback={<p>Loading…</p>}> {/* the fallback goes into the static shell */} <Product params={params} /> </Suspense> );}
async function Product({ params }: Props) { const { id } = await params; const product = await getProduct(id); return ( <main> <h1>{product.name}</h1> <AddToCart id={id} /> {/* only this component's JS goes to the browser */} </main> );}
async function getProduct(id: string) { 'use cache'; // Cache Components: the result is cached by arguments cacheLife('hours'); cacheTag(`product-${id}`); // reset after a change: updateTag in a Server Action const res = await fetch(`https://api.example.com/products/${id}`); return (await res.json()) as { name: string };}'use server';import { updateTag } from 'next/cache';
export async function renameProduct(id: string, formData: FormData) { const session = await auth(); // a public POST endpoint: check permissions here if (!session?.isAdmin) throw new Error('Forbidden'); await db.products.update(id, { name: String(formData.get('name')) }); updateTag(`product-${id}`); // the author sees the new name at once}
// app/api/products/route.ts — an HTTP API for external clientsexport async function GET() { return Response.json(await db.products.list());}- How does a Server Component differ from a Client Component? — Its code never reaches the browser and it can be
async, but it has no state or events. - Can a Server Component be nested in a Client Component? — Importing it turns it into a client one; pass it as
childrenor another prop. - What happened to middleware in Next.js 16? — Renamed to
proxy.tswith aproxyfunction on Node.js;middleware.tsfor Edge is deprecated. - How does
updateTagdiffer fromrevalidateTag? —updateTagin a Server Action shows fresh data immediately;revalidateTagserves stale data and refreshes in the background. - When a Route Handler, and when a Server Action? — An action for mutations from your own UI, a Route Handler for an HTTP API used by other clients and webhooks.
What’s new in React 19, and how do you get fast rendering?
Section titled “What’s new in React 19, and how do you get fast rendering?”What it is
- Action — a function passed to
<form action={...}>: it receivesFormData, and React knows whether submission is in progress. useActionState— the result of the last action and anisPendingflag.useOptimistic— a temporary value shown while the action runs.- React Compiler — a build plugin that caches computations and skips unnecessary renders by itself.
How to use
const [error, formAction, isPending] = useActionState(save, null).<form action={formAction}>and a button withdisabled={isPending}.- In Next.js 16 the compiler is enabled with
reactCompiler: true; in Expo SDK 54+ it is on by default.
Common mistake
Wrapping every function in useCallback “for speed”. Without measurements it is noise, and with the compiler it is dead code.
Takeaway: Actions remove hand-written loading flags, and the compiler removes most manual memoization.
How it works
- Transition.
startTransitionmarks an update as non-urgent: input stays responsive, and the old UI stays visible while the new one is prepared. An Action is an async function inside a transition. useOptimistic. Call the setter inside an Action. When the action finishes, the optimistic value is replaced by the real one; on error it disappears.use. Reads a promise, suspending the component until it resolves, or a context. Unlike other hooks, it can be called in conditions.- Suspense and streaming. The nearest
Suspenseshows its fallback while a component waits for data. The server sends the shell immediately and appends finished parts to the same response. - Hydration. React attaches handlers to the HTML that came from the server.
Suspenseboundaries hydrate independently, and the one the user interacts with goes first.
Pitfalls
- Hydration error — the server HTML did not match the first client render:
Date.now(),typeof window, locale, a<div>inside a<p>. React re-renders that part on the client, which is slower. - A promise created during a Client Component’s render is recreated for
useon every render. Pass it from a Server Component or get it from a caching library.
Rule of thumb
Measure in the Profiler first, then optimize: move state down, split the page with Suspense, enable the compiler.
Takeaway: React 19 makes pending states and mutations declarative, while streaming and selective hydration speed up the first load.
Under the hood
- Server Components run on the server and are serialized into the RSC Payload; their code and dependencies never reach the bundle.
- The
'use client'boundary is a serialization boundary. Props that can cross it: primitives, plain objects, arrays,Date,Map,Set, promises, JSX and Server Functions. Regular functions and class instances cannot. - React Compiler 1.0 (October 2025) analyzes data flow at build time and memoizes values, JSX and callbacks, including after an early
return, whereuseMemois impossible. It skips components that break the Rules of React.
Trade-offs
useMemoanduseCallbackremain for precise control, such as a stable effect dependency. Remove existing memoization only with tests: it changes the compiler output.- In Next.js 16 the compiler is not enabled by default and runs through Babel, so builds are slower; a Rust version is experimental since 16.3.
- Streaming improves time to first byte, but every
Suspenseboundary is a separate loading state. Boundaries that are too fine-grained give you a scatter of spinners. - A browser-only component (
localStorage, time zone) —use(browser())from React 19.3 instead of amountedflag set in an effect.
How to check in production
- The React DevTools Profiler: a flamegraph of commits and why each component rendered. Components optimized by the compiler show a “Memo ✨” badge.
- Performance Tracks in Chrome DevTools (React 19.2) show scheduler and component work on the same timeline as the network.
- Hydration errors appear in the console with a server/client diff, and in production via
onRecoverableError.
Takeaway: fast React means less JS on the client thanks to Server Components, streaming split at Suspense boundaries, and automatic memoization instead of manual.
'use client';import { useActionState, useOptimistic } from 'react';import { addComment } from './actions'; // Server Function: (prev, formData) => Promise<string | null>
type Comment = { id: string; text: string };
export function Comments({ comments }: { comments: Comment[] }) { const [optimistic, addOptimistic] = useOptimistic( comments, (list, c: Comment) => [...list, c], ); const [error, formAction, isPending] = useActionState( async (prev: string | null, formData: FormData) => { // Visible immediately, before the server responds; on error the row disappears addOptimistic({ id: crypto.randomUUID(), text: String(formData.get('text')) }); return addComment(prev, formData); // error text or null }, null, );
return ( <form action={formAction}> <ul>{optimistic.map((c) => <li key={c.id}>{c.text}</li>)}</ul> <input name="text" /> <button disabled={isPending}>Send</button> {error && <p role="alert">{error}</p>} </form> );}// app/orders/page.tsx — a Server Componentimport { Suspense } from 'react';import { OrderList } from './order-list';
export default function Page() { const orders = getOrders(); // no await: the request starts right away return ( <Suspense fallback={<p>Loading…</p>}> {/* the fallback goes into the HTML first */} <OrderList orders={orders} /> {/* a promise is serializable, so it can be passed */} </Suspense> );}
// app/orders/order-list.tsx'use client';import { use } from 'react';
export function OrderList({ orders }: { orders: Promise<{ id: number }[]> }) { const list = use(orders); // suspends the component until the response return <ul>{list.map((o) => <li key={o.id}>#{o.id}</li>)}</ul>;}- What can you pass from a Server Component to a Client Component? — Serializable values: primitives, plain objects, arrays,
Date,Map,Set, promises, JSX, Server Functions; not regular functions or classes. - How does
useActionStatediffer fromuseFormStatus? — The former holds the result and pending state of a specific action;useFormStatusfromreact-domreads the parent form’s status in a child component. - Do you still need
useMemoanduseCallbackwith React Compiler? — Rarely: for precise control, such as a stable effect dependency; remove old memoization only with tests. - How do you fix a hydration error? — Remove the server/client difference: browser-only logic goes into an effect or
use(browser());suppressHydrationWarningis only for text like a date. - How does
useTransitiondiffer fromuseDeferredValue? — The former marks your own update as non-urgent; the latter defers a value whose update you don’t control.