"use client" Error in Next.js: useState, Context, onClick

Updated 8 min read

TL;DR A "use client" error in Next.js means a Server Component imported code that needs the browser: a hook, createContext or an event handler. Add "use client" to that component's file, or re-export a third-party component from your own client wrapper, keep page.tsx a Server Component, and pass only serializable props.

On this page
  1. Why does Next.js throw "use client" errors?
  2. Error 1: "You're importing a component that needs useState"
  3. Fix for your own components: put the directive in the component file
  4. Fix for a library you can't edit: a one-line wrapper file
  5. Error 2: "createContext only works in Client Components"
  6. Fix: a client provider component, rendered from a server layout
  7. When the error comes from inside a library
  8. Error 3: "Event handlers cannot be passed to Client Component props"
  9. Fix: define the handler inside the client component
  10. What does a page that builds look like?
  11. Why does useRef say "Expected 1 arguments, but got 0" in React 19?
  12. A checklist for pasting components into Next.js
  13. Why does importing one component from a barrel file break?
  14. Where do Motion and animation libraries fit?
  15. FAQ
  16. How do I fix "useState only works in Client Components" in Next.js?
  17. What does "createContext only works in Client Components" mean?
  18. Why can't I pass an onClick from a server page to a client component?
  19. Should I put "use client" at the top of page.tsx?

A "use client" error in Next.js means a Server Component pulled in code that only works in the browser. In the App Router every file is a Server Component unless it, or a file that imports it, starts with "use client". So when you paste a component that uses useState, useEffect, createContext or an onClick handler into a page, the build fails until that component sits behind a "use client" boundary. The fix is to add the directive at the top of the component file (or a small wrapper file for a third-party library), keep your page.tsx a Server Component, and pass data down as props.

I hit the first one and the useRef one when I moved my own animated components into a fresh Next.js 15 app, and reproduced the rest in the same setup. Below are the exact errors I got, why each happens and the fix that builds. Everything was tested with Next.js 15.5, React 19.1 and TypeScript strict mode, using next build and tsc --noEmit.

Why does Next.js throw "use client" errors?

Server Components run once on the server and send rendered output to the browser. They have no state, no effects and no event handlers, because none of their code runs in the browser. The Next.js docs on Server and Client Components explain the model in detail.

"use client" marks the point where code starts being sent to the browser. It applies to the file it's in and to everything that file imports. You don't need it in every file, only at the entry point of each interactive part of your tree.

Most component libraries, snippets from blog posts and code generated outside a Next project were written for client-only React, where this boundary doesn't exist. That's why copying them in breaks.

Hold to Delete running live. A delete button you press and hold, then the file crumples into the bin.

Error 1: "You're importing a component that needs useState"

This is the most common one. I made a file that stands in for a library that uses useState and ships without the directive:

tsx
// Stands in for a third-party package that uses hooks but ships no "use client" directive.
import { useState } from "react";

export function FancyCarousel({ slides }: { slides: string[] }) {
  const [index, setIndex] = useState(0);
  return (
    <div>
      <p>{slides[index]}</p>
      <button type="button" onClick={() => setIndex((index + 1) % slides.length)}>
        Next slide
      </button>
    </div>
  );
}

Then I imported it straight into a server page:

tsx
import { FancyCarousel } from "@/lib/vendor/fancy-carousel";

export default function Page() {
  return <FancyCarousel slides={["One", "Two", "Three"]} />;
}

next build stopped with this:

Failed to compile.

./src/lib/vendor/fancy-carousel.tsx
Error:   x 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.

Import trace for requested module:
./src/lib/vendor/fancy-carousel.tsx
./src/app/page.tsx

Read the import trace from the bottom up. It shows which server file pulled in the hook, and that tells you where the boundary is missing.

Fix for your own components: put the directive in the component file

If you own the file, add "use client" as its first line. Here is a counter that uses state, an effect and a ref:

tsx
"use client";

import { useEffect, useRef, useState } from "react";

export function Counter({ initial, label }: { initial: number; label: string }) {
  const [count, setCount] = useState(initial);
  const timer = useRef<number | null>(null);

  useEffect(() => {
    return () => {
      if (timer.current !== null) window.clearTimeout(timer.current);
    };
  }, []);

  function bump() {
    setCount((c) => c + 1);
    if (timer.current !== null) window.clearTimeout(timer.current);
    timer.current = window.setTimeout(() => console.log("saved", label), 500);
  }

  return (
    <button type="button" onClick={bump}>
      {label}: {count}
    </button>
  );
}

Don't put "use client" on page.tsx just to make the error go away. It works, but the whole page and everything it imports ends up in the browser bundle, and you can no longer fetch data with async and await directly in the page.

Fix for a library you can't edit: a one-line wrapper file

When the component lives in node_modules, you can't add the directive to it. Create a file of your own that re-exports it:

tsx
"use client";

export { FancyCarousel } from "@/lib/vendor/fancy-carousel";

With a real package you'd write export { Carousel } from "some-carousel". Import the wrapper everywhere instead of the package, and the boundary is in one place. The Next.js docs describe the same pattern for third-party components.

Error 2: "createContext only works in Client Components"

Theme providers, toast providers and most animation libraries' config components use React context. Context is client-only, so this fails:

tsx
import { createContext } from "react";

const ThemeContext = createContext("light");

export default function Page() {
  return (
    <ThemeContext.Provider value="dark">
      <p>Hello</p>
    </ThemeContext.Provider>
  );
}

The build error uses the same wording as Error 1:

./src/app/page.tsx
Error:   x You're importing a component that needs `createContext`. This React Hook only works in a Client Component. To fix, mark the file (or its parent) with the `"use client"` directive.

Fix: a client provider component, rendered from a server layout

Move the context and the provider into a client file:

tsx
"use client";

import { createContext, useContext, useState } from "react";

type Theme = "light" | "dark";
const ThemeContext = createContext<{ theme: Theme; toggle: () => void } | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>("light");
  const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
  return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error("useTheme must be used inside <ThemeProvider>");
  return ctx;
}

The layout stays a Server Component and just renders the provider:

tsx
import { ThemeProvider } from "./ThemeProvider";

export default function Layout({ children }: { children: React.ReactNode }) {
  return <ThemeProvider>{children}</ThemeProvider>;
}

The children you pass through a client provider can still be Server Components. They're rendered on the server and passed in as already-rendered output, so wrapping your app in a provider doesn't turn the whole app into client code.

When the error comes from inside a library

The wording changes when createContext is called inside a package rather than in your file. A Stack Overflow question about Material UI (over 50,000 views) and an issue in the Emotion repo quote it as "TypeError: createContext only works in Client Components. Add the "use client" directive at the top of the file to use it." I didn't reproduce that exact variant in my test app, but the cause is the same: a server file imported a styling or UI library that creates context when it loads. CSS-in-JS libraries that inject styles at runtime are the usual suspects. The fix is the same pair of moves: put the library's provider in your own client component, and import the library's components through a "use client" wrapper. If the library has no App Router support at all, check its docs for a newer version before you build workarounds.

Error 3: "Event handlers cannot be passed to Client Component props"

This one shows up at prerender time, not compile time. A server page tried to pass a function to a client button:

tsx
import { LikeButton } from "./LikeButton";

export default function Page() {
  return <LikeButton onLike={() => console.log("liked")} />;
}

The build compiled, then failed while generating static pages:

Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Event handlers cannot be passed to Client Component props.
  {onLike: function onLike}
           ^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.

Props that cross from server to client have to be serializable: strings, numbers, plain objects, arrays, JSX. A function defined on the server can't be sent to the browser. (Server Actions are the exception, and they're a different tool.)

Fix: define the handler inside the client component

Pass data, and let the client component own the behavior:

tsx
"use client";

export function LikeButton({ postId }: { postId: string }) {
  function handleClick() {
    console.log("liked", postId);
  }
  return (
    <button type="button" onClick={handleClick}>
      Like
    </button>
  );
}

What does a page that builds look like?

Here is the page that combines all the fixes. It's still an async Server Component, it loads data on the server, and only the small interactive parts are client components:

tsx
import { FancyCarousel } from "@/components/client/fancy-carousel";
import { Counter } from "./Counter";
import { LikeButton } from "./LikeButton";
import { ThemeButton } from "./ThemeButton";

async function getStats() {
  // in a real app this is a database or fetch call; it runs on the server only
  return { stars: 128, title: "Release notes" };
}

export default async function Page() {
  const stats = await getStats();
  return (
    <main>
      <h1>{stats.title}</h1>
      <Counter initial={stats.stars} label="Stars" />
      <LikeButton postId="release-notes" />
      <ThemeButton />
      <FancyCarousel slides={["One", "Two", "Three"]} />
    </main>
  );
}

ThemeButton is a three-line client component that calls useTheme(). This page built and prerendered as static content.

Why does useRef say "Expected 1 arguments, but got 0" in React 19?

This one isn't about the directive, but it shows up at the same moment, because next build type-checks your code. React 19's types made the argument to useRef required. The React 19 upgrade guide lists it as a breaking change in TypeScript. A lot of older component code has this line:

tsx
const timer = useRef<number>();

With @types/react 19, tsc --noEmit reports:

src/app/Timer.tsx(6,17): error TS2554: Expected 1 arguments, but got 0.

Give it an initial value and include that value in the type, as in the Counter above:

tsx
const timer = useRef<number | null>(null);

Then check for null before you use it.

A checklist for pasting components into Next.js

When a component from a library, a generator or a blog post fails in the App Router:

  1. Read the import trace and find the first file that uses a hook, context or a handler.
  2. If you own that file, add "use client" at the top.
  3. If you don't own it, re-export it from your own "use client" wrapper file.
  4. Keep page.tsx and layout.tsx as Server Components, and pass serializable props down.
  5. Move event handlers into the client component.
  6. Run npx tsc --noEmit before next build to catch React 19 type changes like useRef().

If you're shopping for animated components, check whether each file already starts with "use client" and passes a strict type check. It's a quick way to tell whether the author ever ran it in Next.js. I learned that one the hard way: my hold-to-delete button had exactly the useRef<number>() line above, and none of my files had the directive. The free piece on /free and the components in my animated navigation bars collection are the ones I'd test first with this checklist.

Why does importing one component from a barrel file break?

This is the one that confuses people with a shared UI package or a monorepo. A GitHub discussion on the Next.js repo titled "Use 'use client' or die" describes it well: someone imports a component that uses no hooks from packages/ui, through an index.ts that re-exports every component, and the build fails on a different component they never used.

The reason is that a barrel file is just a module. When a Server Component imports anything from it, the bundler follows every export ... from line in that file, and any hook-using component in the list is now imported from a server file. The error's import trace then points at the barrel, not at your page.

Three ways to fix it, in the order I'd try them:

  1. Put "use client" at the top of each component file in the package that uses hooks, context or handlers. Then the barrel is safe to import from anywhere. Outside a React Server Components setup the directive is just a string at the top of the file (some bundlers print a warning about it), so the same package still works in a plain Vite app.
  2. Import the specific file (@repo/ui/components/Button) instead of the barrel, so the server file only pulls in what it uses.
  3. Keep a second entry point for client components (@repo/ui/client) that starts with "use client" and re-exports only those.

Where do Motion and animation libraries fit?

Animated components hit these errors more than anything else, because they all run in the browser. Motion (the library formerly called Framer Motion) needs a client boundary like any hook-based package. Its install docs show two options for Next.js: add "use client" to the file that renders motion.div, or import * as motion from motion/react-client, which the docs offer as a way to send less JavaScript to the browser. Either way, keep the animated part small and the page around it on the server.

The same goes for components you paste from a catalog or a generator: if the file uses useState, useEffect, useRef with a DOM node, or an onClick, it needs the directive or a wrapper.

I make animated React components at qodesign, and two of these errors came from my own catalog before I fixed them.

FAQ

How do I fix "useState only works in Client Components" in Next.js?

Add "use client" as the first line of the file that calls useState. If that file is inside a package you can't edit, create your own file that starts with "use client" and re-exports the component, then import that wrapper instead.

What does "createContext only works in Client Components" mean?

React context runs only in the browser, and a Server Component or a library imported from one tried to create it. Move the context and its provider into a client component, then render that provider from your server layout with the rest of the app as children.

Why can't I pass an onClick from a server page to a client component?

Props going from a Server Component to a Client Component must be serializable, and a function isn't. Pass data like an id instead and define the click handler inside the client component.

Should I put "use client" at the top of page.tsx?

It makes the error go away, but it turns the whole page and everything it imports into browser code, and you lose async data fetching in the page. Put the directive on the smallest interactive component instead.