Fixing \"You're importing a component that needs useState\"

This build error means a Server Component's module graph reached a hook that only works in a Client Component. Here is the smallest repro, the cause, and the two fixes.

6 min read

This error means a Server Component's module graph reached a hook that needs a live client instance, with no client boundary anywhere in between. It shows up after adding state to a component you assumed was already interactive, or after importing something that quietly calls that hook itself. This article walks through the smallest reproduction, the plain-language cause, and both ways to fix it.

The error

This is the exact message Next.js prints once its build-time check catches the hook before the route ever serves a real request.

texttext
Error: 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", so they're Server Components by default.
 
Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component

Next.js is saying that somewhere in this file's import chain, a component calls useState, but neither that file nor any parent above it has opted in with the client directive. Every file in the App Router is a Server Component by default, and a Server Component cannot run this hook.

The smallest reproduction

A plain file in the app folder is a Server Component unless it says otherwise. Calling the hook inside one fails the build instead of rendering.

App.tsxApp.tsx
// app/products/product-filter.tsx
import { useState } from 'react'
 
export default function ProductFilter() {
  const [inStock, setInStock] = useState(false)
  return <button onClick={() => setInStock(!inStock)}>{inStock ? 'Showing in stock' : 'Show all'}</button>
}

Importing this file into a page and building the app produces the error above. Next.js traces the module graph while building the route, finds the hook inside a file with no client boundary, and stops the build right there.

Why this happens

The hook needs a component instance that stays alive in the browser, so React can store a value between renders and re-render when it changes. A Server Component has none of that. It runs once on the server to produce output, then the function is done, so there is nowhere for a stored value to live or for a re-render to happen.

Next.js checks for this while building rather than letting it fail silently or at runtime for a real visitor. That is why the build stops instead of shipping a button that does nothing when someone actually clicks it.

Fix: add use client to the file

If the whole component is meant to be interactive, add "use client" to the top of that file, above every import.

App.tsxApp.tsx
// app/products/product-filter.tsx
'use client'
 
import { useState } from 'react'
 
export default function ProductFilter() {
  const [inStock, setInStock] = useState(false)
  return <button onClick={() => setInStock(!inStock)}>{inStock ? 'Showing in stock' : 'Show all'}</button>
}

The directive turns this file, and everything it imports and renders directly, into client code. The button still renders once on the server to produce the initial HTML, then React hydrates it in the browser so the click handler and state become active.

Fix: extract the stateful piece into its own file

When the file with the hook is a large section of a page, converting the whole thing pulls unrelated markup and data fetching into the client bundle too. Move only the part that needs state into a small file of its own instead.

App.tsxApp.tsx
// app/products/page.tsx
import ProductFilterToggle from './product-filter-toggle'
import { getProducts } from '@/lib/data'
 
export default async function ProductsPage() {
  const products = await getProducts()
  return <section><ProductFilterToggle />{products.length} products</section>
}

The products page stays a Server Component, so it keeps fetching data directly and never ships that code to the browser. Only the toggle button below needs the hook.

App.tsxApp.tsx
// app/products/product-filter-toggle.tsx
'use client'
 
import { useState } from 'react'
 
export default function ProductFilterToggle() {
  const [inStock, setInStock] = useState(false)
  return <button onClick={() => setInStock(!inStock)}>{inStock ? 'Showing in stock' : 'Show all'}</button>
}

This small file is the only Client Component in the pair. The rest of the page's markup and its data fetching stay out of the client bundle entirely, because this is the only file the directive touches.

Confirming the fix

Rebuild the app or reload the route in development. The build should finish, or the dev server should compile the route, with no message about the hook or a missing client directive.

bashbash
npm run build

If the button now renders and clicking it updates its own text, the hook has a client component to run in and the fix worked.

Which fix to choose

Add the directive to the whole file when the component is small and every part of it is already tied to the interactive behavior, such as a search box or a toggle button. Extract a smaller client file when the hook only affects one piece of a larger page that otherwise fetches data or renders static content.

Converting a whole page or layout just to silence this error is the most common overcorrection. It works, but it moves markup and data fetching that never needed to run in the browser into the client bundle along with the hook. See When to Add use client and When Not To for how to decide where that line should sit.

For the broader rules on what each component type can do, see Server Components vs Client Components in Next.js. For the mechanics of the directive itself, see The use client Directive Explained.

Rune AI

Rune AI

Key Insights

  • The error means useState was reached from a Server Component's import graph with no use client boundary in front of it.
  • Server Components render once on the server and have no live instance for state to attach to.
  • Add use client to the top of the file that calls the hook, above every import.
  • If only one small part needs state, extract that part into its own file instead of converting the whole component.
  • The fix location matters because use client marks the whole file's module graph as client code, not just one function.
  • Rerun the build or reload the route to confirm the error is gone.
RunePowered by Rune AI

Frequently Asked Questions

Is this the same error as "useState only works in Client Components"?

They point to the same underlying rule. Next.js has shown both wordings across versions and build tools, but both mean a hook that needs a live client instance was reached from a Server Component's module graph.

Do I need to add use client to every file in the import chain?

No. Add it once, in the file that actually calls the hook. Every component that file imports and renders directly is treated as a Client Component automatically, so parent files stay untouched.

Why does the error name a hook I never imported myself?

Next.js follows the import chain during the build. If a helper or a third-party component you imported calls useState without its own use client boundary, the error surfaces at the first Server Component that reached it.

Will adding use client fix this even if the state only matters for one small part of the page?

It fixes the build, but it also moves everything that file imports into the client bundle. For a small stateful piece inside a larger server-rendered page, extracting just that piece into its own file is usually the better fix.

Conclusion

This error means a Server Component's module graph reached useState without a client boundary in between. Add use client to the file that calls the hook, or extract just the stateful piece into its own small client file, and the build error goes away because the hook now has a client component to run in.