Fix: component needs useState, no Client parent
Client-only hooks need a client boundary. Add "use client" at the entry point and keep server-only imports outside its client dependency graph.
You're importing a component that needs useState. It only works in a Client Component, but none of its parents are marked with "use client" appears
when a module uses a client-only hook without belonging to a
"use client" import graph. The message names useState, but the same
error also concerns client-only hooks such as useEffect and useReducer.
It does not mean that every React hook is browser-only, or that Client Components
cannot be prerendered on the server.
The one-line fix — and why it's often in the wrong file#
The fix is adding "use client" as the very first line of the file that
calls the hook:
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}Adding the directive to the file that imports Counter also works, because
the import joins that module's client dependency graph:
// app/page.tsx
'use client'; // Valid boundary: imported Counter is also client code.
import { Counter } from '@/components/Counter';
export default function Page() {
return <Counter />;
}This is a valid fix, not a missing-directive check being bypassed. The
official directive reference
explains that every client file does not need its own directive. The tradeoff is
the size of the client import graph: placing the boundary on a small interactive
component keeps unrelated imports out of that graph. It does not convert Server
Components passed as children by a server parent into client imports. Server-only
code must remain outside the client dependency graph; marking a page as client
does not make its database access or async server-component code browser-compatible.
The barrel-file version of this bug#
A plain barrel re-export does not remove a component's existing client boundary:
// components/index.ts — barrel file
export { Counter } from './Counter';
export { Header } from './Header';// app/page.tsx — Server Component
import { Counter } from '@/components'; // through the barrelIf Counter.tsx itself has "use client" this works — the directive lives
with the component, and re-exporting does not strip it. The failure mode to
watch for is a barrel file that imports the hook and forwards a wrapped
version, which must itself be inside a client boundary if it calls client-only hooks.
When a Server Component imports that wrapper directly, mark the wrapper's entry file:
// components/index.tsx
'use client';
import { useState } from 'react';
import { Counter } from './Counter';
export function CounterWithLabel() {
const [visible, setVisible] = useState(true);
return (
<div>
<button onClick={() => setVisible(!visible)}>Toggle counter</button>
{visible && <Counter />}
</div>
);
}When a third-party component triggers it#
If the hook lives inside a library you cannot edit (a UI kit, a chart package), wrap it in your own Client Component instead of trying to patch the library:
// components/ChartClient.tsx
'use client';
import { InteractiveChart } from 'some-chart-lib';
export function ChartClient(props: React.ComponentProps<typeof InteractiveChart>) {
return <InteractiveChart {...props} />;
}// app/page.tsx — Server Component
import { ChartClient } from '@/components/ChartClient';
export default async function Page() {
const data = await getChartData();
return <ChartClient data={data} />;
}Data fetched on the server passes through as a plain prop; only the rendering that needs the hook lives on the client.
Verifying the fix#
grep -rln "useState\|useEffect\|useReducer\|useContext" src components appand check whether each module belongs to a client import graph. Add"use client"before imports at the intended entry point when no boundary exists.- Confirm you added the directive to the smallest possible component, not to
layout.tsxorpage.tsx— check withnext buildthat unrelated Server Components in the same file tree still run without a browser bundle. - If a barrel defines a wrapper that uses client-only hooks, verify that wrapper belongs to a client import graph. Plain re-exports preserve existing boundaries; adding ordinary JSX without a client-only hook does not itself require a new one.
Keeping the fetch on the server: pass children instead of converting the tree#
Adding "use client" to the whole page is inappropriate when that page must retain
server-only imports or server-side data loading. A Client Component can still render
Server Components, as long as it receives them through props.children (or any other
prop) rather than importing and rendering them directly. React only requires that the
component instantiating the hook is a Client Component — it does not require every
component nested inside it to be one too, if that nesting happens through composition
instead of a direct import:
// components/ClientShell.tsx
'use client';
import { useState } from 'react';
export function ClientShell({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return <div onClick={() => setOpen(!open)}>{open ? children : null}</div>;
}// app/page.tsx — stays a Server Component
import { ClientShell } from '@/components/ClientShell';
export default async function Page() {
const data = await getData(); // runs on the server, never ships to the browser
return (
<ClientShell>
<ExpensiveServerRenderedList data={data} />
</ClientShell>
);
}ExpensiveServerRenderedList is never imported by ClientShell.tsx, so its module
graph — and anything it imports, including database clients — never enters the client
bundle. page.tsx composes both, but composing is not importing: React resolves
children before it ever crosses the client boundary. This distinction — what "use
client" actually marks versus what people assume it does — is covered end to end in
use client vs use server in Next.js: The Real Difference,
including why "use server" is not its opposite.
The context-provider file: one small client file, not the whole layout#
A related version of this error shows up when a team introduces a React Context (a
theme provider, a query-client provider, an auth-state provider) directly inside
app/layout.tsx and puts "use client" at the top of the layout to make the
createContext/useContext calls work. This moves the layout module and its imports
into the client graph; pages supplied through children can remain Server Components.
A dedicated providers.tsx keeps that client graph smaller while the layout itself
can retain server-only work:
// app/providers.tsx
'use client';
import { createContext, useState } from 'react';
import type { Dispatch, SetStateAction } from 'react';
export const ThemeContext = createContext<{
theme: string;
setTheme: Dispatch<SetStateAction<string>>;
} | null>(null);
export function Providers({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>
);
}// app/layout.tsx — stays a Server Component
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}layout.tsx never gets the directive; only providers.tsx does. The children prop
passed into Providers is resolved by React before crossing into the client module,
the same mechanism as the composition pattern above, which is why pages nested under
this layout keep doing server-side data fetching without any hook-error surprises. The
same "smallest possible client boundary" principle applies to route-level state, not
just context — see how it plays out for URL-driven state in
use client vs use server in Next.js: The Real Difference.
Further reading: Fix: Can't Perform a State Update on an Unmounted Component, Fix localStorage is not defined after 'use client' (Next.js).
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Related fixes & guides
- Fix useEffect Running Twice in React 18 — Strict Mode
- Fix Next.js 'Cannot Have a Negative Time Stamp' Error
- Fix: Event handlers cannot be passed to Client props
- Next.js Performance Optimization for Indie Developers
- Next.js 16 Removed next lint: ESLint 9 Migration
- Next.js 15 Middleware: Auth, Rate Limiting & Edge Guide
- Fix: error while hydrating, root switched to client render
- Fix next/image Layout Shift (CLS) in Next.js
- Adjacent JSX Elements Must Be Wrapped in an Enclosing Tag
- Minified React error #425: Text Mismatch After Deploy

