Why can a React Server Component not call useState or useEffect, or attach an onClick handler to the DOM elements it renders?
answer
- ask what state would even mean here
- it runs once, then it is gone
- no browser instance to re-render
- effects come after a commit
- React points you at 'use client'
basics
~20 sA Server Component runs once on the server and then no longer exists, so nothing in the browser can re-render it, commit an effect, or run a handler for it. React rejects those hooks and tells you to add 'use client'.
solid answer
~50 sAll three restrictions come from the same fact: a Server Component executes once during the server render and leaves no instance behind. State only means something if the component can be re-rendered with a new value, and there is nothing in the browser to re-render — the server already sent its finished output. Effects run after React commits to a DOM, and the server render commits to no DOM at all. An `onClick` needs code in the browser to invoke, but this component's code was never shipped. So the restriction is not React being strict for its own sake; those APIs have no meaning in that runtime. React enforces it explicitly — calling `useState` in a Server Component errors and points you at the `'use client'` directive. The fix is to move the interactive part into a Client Component.
go deeper
Recall the rule plainly: no useState, no useEffect, and no event handlers inside a Server Component — those belong in a component marked with the 'use client' directive.
Derive the rule instead of reciting it: one render with no surviving instance means state has nothing to update, effects have no commit to follow, and handlers have no shipped code to run.
Show how you refactor around it — isolate the smallest interactive leaf as a Client Component so data access and heavy rendering stay on the server, and explain why loud errors beat silent no-ops here.
Be ready to discuss the design principle: the restriction is what makes the two runtimes safely composable, and any abstraction your team builds on top must preserve that error rather than paper over it.
## The single underlying fact Every restriction on Server Components follows from one property: **the component runs once, on the server, and then it is over.** It produces a description of UI, that description is sent to the browser, and the function itself is never called again and never exists in the browser at all. Once you hold that firmly, you do not have to memorise a list of banned APIs — you can derive it. ## Why state is meaningless there `useState` is not really "a place to store a value". It is a request for React to (a) remember a value across renders of this particular component instance and (b) re-render that instance when the value changes. Both halves need a live instance. On the server, the render happens once and the output is serialized. There is no persistent instance: the next request creates a fresh render, and in between there is nothing holding a value. And there is nobody to re-render — the browser holds finished output, not a runnable copy of the component. A setter would have nothing to schedule. So `useState`, `useReducer`, and the other state-bearing hooks are unavailable, and React errors out rather than pretending: ```jsx // Server Component — this throws. export default function Counter() { const [n, setN] = useState(0); // error: add 'use client' to use useState return <p>{n}</p>; } ``` ## Why effects have nothing to run against `useEffect` is defined in terms of the commit: React renders, commits the result to the DOM, paints, and *then* runs your effect, later running its cleanup when the component unmounts or dependencies change. A server render produces no DOM and never mounts anything — there is no commit for the effect to come after, and no unmount for cleanup to come before. This is why the answer "the effect just runs on the server instead" is wrong in an instructive way. Effects exist to synchronise a component with something outside React — a subscription, a timer, an imperative widget, a browser API. All of those are *client* concerns with lifetimes. A server render has no lifetime to synchronise with; it has a beginning and an end a few milliseconds apart. The same reasoning excludes `useLayoutEffect`, DOM refs, and anything touching `window` or `document`. ## Why handlers cannot be attached When you write `<button onClick={fn}>` in a Client Component, React registers the handler in the browser and calls your function there. For that to work two things must be true: the function must exist in the browser, and something must be listening. A Server Component's code never ships. There is no function in the browser to call, so an `onClick` written there cannot ever fire. React treats passing a function across the server→client seam as an error rather than silently dropping it, which is the behaviour you want — a handler that silently does nothing is a much worse bug than a build-time failure. ## Context, too A related restriction worth naming: React does not support context in Server Components. Context is a runtime construct maintained by the renderer as it walks a live tree, and the server side of the boundary does not carry one. Pass values down as props from the Server Component, and read context inside Client Components below the boundary. ## What you do instead The move is always the same: identify the smallest piece that genuinely needs the browser and make *that* a Client Component, leaving the surrounding data-fetching and markup on the server. ```jsx // Server Component: no state, no handlers, just data and structure. export default async function Article({ slug }) { const post = await db.posts.bySlug(slug); return ( <article> <h1>{post.title}</h1> <LikeButton postId={post.id} initialLikes={post.likes} /> </article> ); } ``` `LikeButton` is a `'use client'` module holding the count in state and owning the click. The article body — including whatever heavy Markdown or sanitisation library rendered it — stays on the server. ## How to talk about this in an interview Weak answers recite the rule ("hooks don't work in Server Components"). Strong answers derive it: no persistent instance means no state; no commit means no effects; no shipped code means no handlers. Then name the escape hatch — push the interactive leaf into a Client Component — and note that the restriction is enforced loudly rather than silently, which is what makes the model safe to work in.
- Is a console.log inside a Server Component visible in the browser console?No — it runs on the server, so the output goes to your server process's logs. That is a quick, reliable way to confirm which side of the boundary a component is actually on when you are debugging.
- Can a Server Component read a context value with useContext?No. React does not support context in Server Components — context is maintained by the renderer as it walks a live tree, and the server side of the boundary does not provide one. Pass the value down as an explicit prop instead, or read the context inside a Client Component below the boundary.
- If a Server Component cannot hold state, how does anything on a server-rendered page ever change?Two ways. Interactive state lives in Client Components rendered inside the server output and updates in the browser as normal. Anything that must reflect new server data comes from a new server render — a navigation or a framework-provided refresh — which sends down updated output for that subtree.
saying these in an interview costs you the question
- Says useState works if you never call the setter
- Claims useEffect simply runs on the server instead
- Thinks the restriction is arbitrary React policy
- Suggests useCallback makes an onClick work server-side
- Believes the handler is silently ignored rather than an error