Skip to content

You're importing a module that depends on useState: the fix

You're importing a module that depends on useState: why "use client" sometimes doesn't fix it, the barrel and node_modules traps, and a fix that sticks.

React reactjs nextjs react-server-components debugging
BKG
Reading Progress

On This Page

Everybody's first move is to paste "use client" at the top of whichever file the error names. Sometimes that works. Sometimes the error moves to a different file, sometimes it doesn't move at all, and sometimes the build goes green and the page dies at runtime with a message that doesn't mention "use client" anywhere.

All of those come from the same mistake, and the text of You're importing a module that depends on useState only describes one of the ways it shows up.

Four messages for one mistake

This is what next build printed on Next.js 16.4.3 with React 19.3.1, Turbopack, for a component that calls useState with no directive anywhere above it:

./app/Counter.tsx:1:10
Error: You're importing a module that depends on `useState` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.
    Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
> 1 | import { useState } from "react";
    |          ^^^^^^^^
  2 | export function Counter() {
  3 |   const [n, setN] = useState(0);

Ecmascript file had an error

Import trace:
  Server Component:
    ./app/Counter.tsx
    ./app/page.tsx

The Import trace block is the useful part, and I'll come back to it. With next build --webpack on the same version the sentence is identical, wrapped in webpack's x formatting and followed by a pile of native backtrace noise that tells you nothing.

That's the friendly variant. It only exists for code in your project. The same mistake made inside a package from node_modules gives you one of these instead.

Production build, a library that calls React.useState and ships no directive:

Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: d.useState is not a function or its return value is not iterable
    at ignore-listed frames {
  digest: '2539294684'
}
Export encountered an error on /page: /, exiting the build.

Same library under next dev, where Next rewrites the message before showing it:

⨯ TypeError: useState only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component
 GET / 500 in 446ms

And a library that calls React.createContext while the module loads fails earlier, during Collecting page data:

Error: Failed to collect configuration for /
  [cause]: TypeError: d.createContext is not a function
      at module evaluation (app/page.tsx:1:1)

Notice that Compiled successfully printed right before that one. I'll get to why.

Break it with a three-file Next.js app

I'm using Next's own build instead of a harness this time, because the error comes from Next's compiler and I wanted the real import trace.

mkdir rsc-repro && cd rsc-repro
npm init -y
npm i --save-exact next@16.4.3 react@19.3.1 react-dom@19.3.1 \
  typescript@6 @types/react@19.3.0 @types/node@22
mkdir app
// app/layout.tsx
export default function Root({ children }: { children: React.ReactNode }) {
  return <html lang="en"><body>{children}</body></html>;
}
// app/Counter.tsx
import { useState } from "react";   // no "use client" above this
export function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}
// app/page.tsx  (a Server Component, because nothing says otherwise)
import { Counter } from "./Counter";
export default function Page() { return <main><Counter /></main>; }
npx next build

Confirmation signal: the build stops with the useState message above and an Import trace whose first entry is ./app/Counter.tsx. If it stops with something about createContext instead, you copied a different file than I did. If it succeeds, you probably have "use client" somewhere above Counter in the import graph.

To reproduce the library variant, drop a fake package in place. It's twelve lines and it behaves exactly like the real thing:

mkdir -p node_modules/fancy-ui
echo '{"name":"fancy-ui","version":"1.0.0","main":"index.js"}' > node_modules/fancy-ui/package.json
cat > node_modules/fancy-ui/index.js <<'JS'
const React = require("react");
exports.Tabs = function Tabs() {
  const [i] = React.useState(0);
  return React.createElement("div", null, i);
};
JS
printf 'import { Tabs } from "fancy-ui";\nexport default function Page(){ return <main><Tabs /></main>; }\n' > app/page.tsx
npx next build   # compiles, then dies during prerender

One warning if you run npm i afterward: npm prunes a package that isn't in package.json, and my fake fancy-ui vanished that way. The next build then said Can't resolve 'fancy-ui', which cost me five minutes of wondering what I'd broken.

Next 14, 15, 16: the wording moved twice

I built the same three-file repro against each pairing. Same file, same mistake.

Next.js React Compile-time message for your own file
14.2.35 18.3.1 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.
15.5.28 19.1.10 You're importing a component that needs useState. This React Hook only works in a Client Component. To fix, mark the file (or its parent) with the "use client" directive.
16.4.3 19.3.1 You're importing a module that depends on useStateinto a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the"use client" directive.

(On 14 and 15 I ran next build with its default bundler, webpack. On 16 I ran both Turbopack and --webpack; the text matched.)

This is the part the search results get wrong. Most of the pages that rank for the old sentence, "You're importing a component that needs useState", describe the 14 wording. If you paste your 16 error into search and get nothing useful, that's why. Search for the Next 16 sentence, or search depends on useState and ignore the rest.

The runtime messages for libraries don't depend on the compiler. The rewrite in dev and the raw TypeError in a production build are the same on any version whose React exposes no useState to server code, which is every version in the table.

The React side didn't change at all between 18.3.1 and 19.3.1 for this. The Next side did.

Which file is actually on the server side?

A Server Component, in this world, isn't a kind of file. It's the default state of every module in the app tree until something above it in the import graph carries a "use client" directive. The directive marks a boundary. Everything the marked file imports comes along with it. Everything that imports the marked file from the server does not.

So "mark the file" is a claim about the graph, and the graph has more edges than you think. Three ways the edge you're looking at isn't the one you'd guess:

  • Your page imports a barrel (index.ts) that re-exports a hook file. You only use formatN from it. Still fails.
  • The file you marked says "use client" in the wrong place, so the directive is dead text.
  • The import comes from a package, where Next's compile-time check doesn't look.

I tested the barrel one because I wasn't sure. app/barrel.ts re-exported useThing from a hook file with no directive, plus a harmless formatN. page.tsx imported only formatN. Turbopack still failed, and the import trace named all three:

./app/useThing.ts:1:10
Error: You're importing a module that depends on `useState` into a React Server Component module. ...

Import trace:
  Server Component:
    ./app/useThing.ts
    ./app/barrel.ts
    ./app/page.tsx

(Webpack failed the same way.) The barrel is evaluated as a whole on the server, so the unused export doesn't save you. The trace is the only reason I'd have found useThing.ts at all; the page itself never mentions it.

Why a server React has no useState

This is the layer nobody explains, and it makes everything above predictable.

The react package has two entry points, chosen by a bundler condition. Its exports map looks like this in 19.3.1:

{ "react-server": "./react.react-server.js", "default": "./index.js" }

Next resolves react with the react-server condition for any module on the server side of the boundary. That file is a deliberately cut-down React. I loaded both from the same install:

node -e "const r=require('react'); console.log(typeof r.useState)"
# function
node --conditions=react-server -e "const r=require('react'); \
  console.log(typeof r.useState, typeof r.createContext, typeof r.useMemo, typeof r.use, typeof r.cache)"
# undefined undefined function function function

So on the server side useState isn't forbidden. It doesn't exist. useMemo, use and cache do. The shipped react.react-server.development.js exports Children, cloneElement, createElement, createRef, forwardRef, isValidElement, lazy, memo, use, useCallback, useDebugValue, useId, useMemo, cache, captureOwnerStack and a few more, and no useState, useEffect, useRef, useReducer, useContext or createContext.

Now the four messages stop looking unrelated. When your own code is compiled, Next's compiler spots the import { useState } from "react" in a server-side module and gives you the readable error with the import trace. When a package in node_modules calls React.useState, nothing inspects it at compile time, the build prints Compiled successfully, and the code fails the moment it runs and finds undefined where a function should be. That's the d.useState is not a function. The d is a minified local name for the React module. (And it's why the createContext one fires early: libraries create their context at import time, so the throw happens while Next is loading the page module to read its config, not while rendering.)

The nicer dev message isn't React's. It comes from a small function in Next, format-server-error.js, that pattern-matches the runtime error and rewrites it. I read it in node_modules/next/dist/lib: it holds a list of hook names (useState, useEffect, useReducer, useRef, useTransition, useOptimistic, useSyncExternalStore, useLayoutEffect and others), tests the message against \b<hook>\b.*is not a function, and swaps in the "only works in Client Components" text. It has a separate branch for createContext is not a function with its own docs link, and a third that fires on Class extends value undefined is not a constructor or null to tell you that a class component can't render on the server. I didn't trigger that third one; I'm reporting what the source says.

That rewrite is a dev-only convenience. It doesn't run in the production build, which is why the same library gives you d.useState is not a function or its return value is not iterable in a CI log and the friendly sentence on your laptop.

Tracing it when the stack points at node_modules

I walked three wrong turns on the way here.

Wrong turn 1: the directive that wasn't. I wrote import { useState } from "react"; on line 1 and "use client"; on line 2, because that's the order in my head when I'm editing quickly. Turbopack reported two errors, not one:

./app/Counter.tsx:2:1
Error: The "use client" directive must be placed before other expressions. Move it to the top of the file to resolve this issue.
  1 | import { useState } from "react";
> 2 | "use client";

Then the original useState error, still there, because a directive that isn't the first statement is just a string. I'd read only the first error in my first pass and wondered why my fix did nothing. The directive has to come before any import (a comment above it is fine).

Wrong turn 2: it's the bundler. After the webpack output filled with a native stack trace, I suspected Turbopack of misreading the graph and reran with --webpack. Same sentence, same file. I dropped it in one run. The bundler is the wrong layer here; the compiler pass that does this check is identical either way.

Wrong turn 3: waiting for the friendly error. I'd just seen the compile-time message, so with the fake library in place I expected one. The build printed Compiled successfully and I spent a while searching my own app folder for a hook. There was none. The stack's only application frame was app/page.tsx:1:1, the line that imports the package. That frame was the whole clue.

Now the path I'd use in an app that isn't mine:

# 1. Is it your code or a package?
#    Compile-time error with "Import trace:" -> your code. Go to step 2.
#    Build says "Compiled successfully" then fails in prerender or page data -> a package. Go to step 3.

# 2. Read the Import trace from the TOP entry down.
#    The first file listed is the one that calls the hook. Mark that file, or
#    (better) the nearest leaf component that needs state. Barrel in the trace? See step 4.

# 3. Find which package. The failing frame is the import line in your page/layout.
#    A dev run (next dev) prints the rewritten "useState only works in Client
#    Components" line in the terminal. Then look inside the package:
grep -l "use client" node_modules/<pkg>/dist/*.js | head   # no hit = no directive shipped
grep -ln "useState\|createContext" node_modules/<pkg>/dist/*.js

# 4. Barrel in the trace?
#    Import from the leaf file directly, or put "use client" on the leaf that uses the hook.

# 5. Check the thing you assumed: is the directive really first?
head -n 3 app/Whatever.tsx
build output
├─ "Import trace" printed ───────────► your file ──► mark the leaf (cause 1, 3, 4)
├─ Compiled OK, then
│   ├─ "d.createContext is not a function" ──► a package, load time (cause 2)
│   └─ "d.useState is not a function ..."  ──► a package, render time (cause 2)
└─ two errors, one about "directive must be placed before" ──► cause 3

Here's how often I'd expect each one to be the culprit. These ranks are my judgment from the code paths I read and the cases I reproduced, not a measured count.

Rank Cause How often The tell Fix
1 Your own hook or context in a module with no directive above it in the graph Most cases Compile error plus Import trace Option A
2 A package that doesn't ship "use client" Common Compiled successfully, then d.<hook> is not a function in prerender Option B
3 A barrel re-exporting a hook file you don't use Common in bigger apps Trace lists index.ts between page and hook file Option C
4 "use client" present but not the first statement Occasional A second error: "must be placed before other expressions" Fix the order
5 A class component (Class extends value undefined) rendered from a server file Rare Error text mentions Class extends, per Next's rewrite table Option A

The one that cost me time was 2, and the tell is the combination: green compile, red prerender, and the only app frame is an import line.

Where the "use client" line goes

Why this works: you're moving the boundary so the file that needs state, effects or context is on the client side, and the server keeps everything it can.

 // app/Counter.tsx
+"use client";
 import { useState } from "react";
 export function Counter() {

That's the fix for your own code (Option A). Put it on the leaf that uses the hook, not on page.tsx or layout.tsx. Marking the page works and quietly turns the whole subtree into client code, which is how a small widget turns into a much heavier route.

Option B is for a package. You can't edit node_modules, so wrap it once in a file you own:

// app/fancy.tsx
"use client";
export { Tabs } from "fancy-ui";
// app/page.tsx
import { Tabs } from "./fancy";
export default function Page() { return <main><Tabs /></main>; }

With the wrapper, my build finished clean and the route prerendered. The wrapper is the right place for the fix because the boundary belongs in your tree, and when the package finally adds its own directive, you delete one file.

Option C is for barrels. Import the leaf file directly (./useThing, not ./barrel), or split the barrel so hook files and plain helpers don't share one. Re-exporting a hook file from a barrel that server code imports is the mistake; the hook file itself is fine.

I'd ship A for my code and B for packages, every time. Here's what I wouldn't ship:

  • "use client" on page.tsx or layout.tsx to make the error go away. It does. It also moves data fetching to the browser and drags every import in with it.
  • Deleting the hook and inlining a hack (useMemo instead of useState, a module-level variable instead of state). useMemo exists on the server, so this compiles. The component is then wrong in a quieter way, since a module-level variable is shared across requests.
  • Pinning an old package version until the library fixes itself. Fine as an incident patch, not as a plan.

If a client component starts failing for a different reason, such as it being declared async, that's another error entirely, and the Objects are not valid as a React child article has the version table for it.

Design the boundary before you write the hook

The bug class is "a file doesn't know which side it lives on". Two habits remove most of it.

First, keep the server file as the data owner and hand it small interactive children. Don't make a big component interactive and then fetch inside it.

// app/products/page.tsx   (Server Component, no directive)
import { Filters } from "./Filters";
import { loadProducts } from "./data";

export default async function Page() {
  const products = await loadProducts();       // runs on the server
  return (
    <main>
      <Filters />                              {/* the only client island */}
      <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>
    </main>
  );
}
// app/products/Filters.tsx
"use client";
import { useState } from "react";

export function Filters() {
  const [q, setQ] = useState("");
  return <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Filter" />;
}

I built that under 16.4.3 and it prerendered. The rule I follow: a file with a hook in it gets the directive on line 1 the moment I write the hook, not after the build complains.

Second, make the boundary explicit for shared code. The client-only package makes a module fail if a server file imports it, and the failure is a readable one. With client-only@0.0.1, importing it from a server file gave me:

Error: You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
...
Error: 'client-only' cannot be imported from a Server Component module

(Yes, that's the 14-era wording again, on 16.4.3. Different check, older string.) The server-only package does the mirror job for modules that must never reach the browser; I only ran client-only, but it's the same mechanism.

Hook-using helpers also deserve a naming rule. A file called useX.ts or *.client.ts signals what it needs before anyone imports it from the wrong side. Context providers are the usual offender: one small Providers.tsx with the directive, imported by layout.tsx, and everything else stays out of it.

This also connects to hydration. A component that renders different output on server and client is a close cousin of this mistake, covered in the hydration mismatch article. And if the failing import came from two copies of React rather than a missing directive, the Invalid hook call article is the right tab.

Making the build fail before a user does

  • The build is your gate. For your own code, next build fails loudly with the trace. Run it in CI on every PR, not only on main.
  • For packages, a build isn't enough. Mine said Compiled successfully before failing, and a page you never prerender at build time can hide the failure until traffic arrives. A Playwright test that loads every route, failing on any console.error or 500, is the net.
  • Dependency bumps. Add a smoke route that imports each UI package you use from a Server Component. A package that drops its directive in a minor release shows up there first.
  • Lint. I didn't find an ESLint rule I could verify that understands the server/client boundary, so I'm not naming one. react-hooks/rules-of-hooks checks hook placement, not which side of a boundary the file is on.
  • Types. None of this is a type error; tsc is happy with a hook in a server file. Don't expect TypeScript to catch it.
  • Production signal. A 500 spike on a route right after a dependency update, with is not a function and a minified single-letter receiver in the log, should send you to this article. Alert on it by route, not only on global error rate.
  • Version safety. The compile-time wording changed twice between Next 14, 15 and 16 (table above) and the runtime behaviour didn't. On your next Next.js upgrade, rebuild with the new version and search CI logs for depends on and needs; both phrasings should be on your list. I verified those three pairings on 2026-10-27, with Next 16.4.3 published 2026-10-23 and React 19.3.1 on 2026-10-14. I couldn't open nextjs.org from the machine I ran this on, so the docs links in the messages are quoted, not checked.
  • Team convention. The directive goes on line 1 of the leaf that holds the hook, and layout.tsx and page.tsx don't get one unless there's a reason written in a comment.

Four lines for the next boundary bug

  1. Read the Import trace from the top. The first file listed is the one that needs the directive.
  2. "Compiled successfully" followed by is not a function means a package, and the app frame is the import line.
  3. A barrel pulls in everything it re-exports, used or not.
  4. Put "use client" on the smallest file that needs it, and check it's really the first statement.

Questions people ask about this error

How do I fix "You're importing a component that needs useState"? Add "use client" as the first statement of the file that calls the hook, or of its nearest parent in the import trace. On Next 16 the same error is worded "You're importing a module that depends on useState".

Why does "use client" not fix it? Usually because it isn't the first statement, because a barrel is importing a different hook file, or because the failing hook lives in a package. Check the import trace, then check for a second error about the directive's position.

Can I use useState in a Server Component? No. In the server build of React 19.3.1 useState is undefined, along with useEffect, useRef, useReducer and createContext. useMemo, use and cache are available.

Why does the error say "d.useState is not a function"? That's a package calling React.useState in server-side code, with the minified name of the React module as d. In dev, Next rewrites it to "useState only works in Client Components"; in a production build it doesn't.

Reactreactjsnextjsreact-server-componentsdebugging

BKG

Engineer and writer behind CODELZ. I read the stack trace, reproduce the bug, and explain why it happens, not just how to silence it. Plus honest reviews of the books and tools worth your time.

Comments