DEV Community

Cover image for Fix: component needs useState, no Client parent
Mahdi BEN RHOUMA
Mahdi BEN RHOUMA

Posted on Originally published at iloveblogs.blog

Fix: component needs useState, no Client parent

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"
fires the
moment a Server Component's render tree reaches a hook call with no
"use client" boundary above it. The message names useState, but the same
error fires identically for useEffect, useReducer, useContext, and any
other hook — React hooks require a component instance that lives in the
browser, and Server Components never do.

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>;
}
Enter fullscreen mode Exit fullscreen mode

The mistake this error usually hides is adding the directive to the file that
imports Counter, not the file that defines it:

// app/page.tsx
'use client'; // ❌ wrong file — Counter still lacks its own directive

import { Counter } from '@/components/Counter';

export default function Page() {
  return <Counter />;
}
Enter fullscreen mode Exit fullscreen mode

This "fixes" the error because now the whole page is a Client Component, so
Counter's missing directive never gets checked. But it silently converts
every Server Component in that subtree — data fetching, async/await
calls, direct database access — into client-rendered code, which is the exact
performance and bundle-size regression the App Router exists to avoid. "use
client"
marks a boundary, not a page-wide switch: put it on the leaf
component that actually needs interactivity, and everything it imports
becomes part of the client bundle, but everything that imports it does not.

The barrel-file version of this bug

The same error appears when the hook-using component is fine, but it is
re-exported through an index file with no directive:

// components/index.ts — barrel file
export { Counter } from './Counter';
export { Header } from './Header';
Enter fullscreen mode Exit fullscreen mode
// app/page.tsx — Server Component
import { Counter } from '@/components'; // through the barrel
Enter fullscreen mode Exit fullscreen mode

If 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 does need its own directive at the top of the barrel:

// components/index.ts
'use client';
export function CounterWithLabel(props) {
  return <Counter {...props} />; // now this file defines new render logic
}
Enter fullscreen mode Exit fullscreen mode

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} />;
}
Enter fullscreen mode Exit fullscreen mode
// app/page.tsx — Server Component
import { ChartClient } from '@/components/ChartClient';

export default async function Page() {
  const data = await getChartData();
  return <ChartClient data={data} />;
}
Enter fullscreen mode Exit fullscreen mode

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

  1. grep -rln "useState\|useEffect\|useReducer\|useContext" src components app and check each file for "use client" as its literal first line — a directive after an import statement is ignored.
  2. Confirm you added the directive to the smallest possible component, not to layout.tsx or page.tsx — check with next build that unrelated Server Components in the same file tree still run without a browser bundle.
  3. If using a barrel file, verify the directive is on the file that actually contains the hook, not assumed to propagate through re-exports of files that already have it (re-exports are fine; new logic in the barrel is not).

Related Incidents


Originally published at https://www.iloveblogs.blog

Top comments (0)