javascriptreactjsnext.js

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"


The simple below component is throwing the following error in Next.js's app directory when I use useState:

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.

import { useState } from "react";

export default function Card() {
  const [state, setState] = useState("");

  return <></>;
}

Solution

  • In the new app directory, by default, Next.js uses Server Components, where the JSX gets compiled to "pure HTML" and sent to the browser. Like any traditional Backend with a templating engine, such as Express with EJS or Laravel with Blade. This is for better performance, as you can read on the doc:

    Server Components allow developers to better leverage server infrastructure. For example, large dependencies that previously would impact the JavaScript bundle size on the client can instead remain entirely on the server, leading to improved performance. They make writing a React application feel similar to PHP or Ruby on Rails, but with the power and flexibility of React for templating UI.

    And a Server Component shouldn't contain browser-specific things like click handlers or hooks such as useState. If you need that, you should add "use client" at the top to tell Next.js to send the JavaScript needed for that component, making it a Client Component:

    "use client"; // This is a client component 👈🏽
    
    import { useState } from "react";
    
    export default function Card() {
      const [state, setState] = useState(""); // I can use client hooks 👈🏽
    
      return <></>;
    }
    

    Now, say you are importing a client-specific library that's not yet marked "use client" by the maintainers. It will work in your Client Components as-is. But to import it into a Server one, you could, for example, create a lib folder at the same level as app in which you add:

    // lib/mui.js
    
    "use client";
    
    export * from "@mui/material";
    

    And import it from there (this way, other parts of the page are still Server Components):

    // app/page.js
    
    import { Button } from "../lib/mui";
    
    export default function Page() {
      return (
        <div>
          <Button variant="contained">Hello World</Button>
        </div>
      );
    }
    

    If you are getting a similar error while setting up a context, the guideline is to add it in its own "use client" marked file:

    // app/theme-provider.tsx
    
    "use client";
    
    import { createContext } from "react";
    
    export const ThemeContext = createContext("");
    
    export default function ThemeProvider({ children }) {
      return (
        <ThemeContext.Provider value="dark">
          {children}
        </ThemeContext.Provider>
      );
    }
    

    And import it from there in your Sever Component:

    // app/layout.js
    
    import ThemeProvider from './theme-provider';
     
    export default function RootLayout({ children }) {
      return (
        <html>
          <body>
            <ThemeProvider>{children}</ThemeProvider>
          </body>
        </html>
      );
    }
    

    Also, you may get a similar error while trying to populate a store. This means your store uses client logic, so you need to move it to a "use client" marked file. And if it's to use data fetched on the server, you could do so:

    // app/layout.js
    
    import PopulateStore from "./populate-store";
    
    const getData = async () => {
      const res = await fetch("https://jsonplaceholder.org/posts");
      return await res.json();
    };
    
    export default async function Layout({ children }) {
      // Fetch data on the server
      const data = await getData();
      return (
        <html>
          <body>
            {/* Pass it to your client store initializer */}
            <PopulateStore data={data}>{children}</PopulateStore>
          </body>
        </html>
      );
    }
    
    // app/populate-store.js
    
    "use client";
    
    export default function PopulateStore({ data, children }) {
      // You can populate your store, whatever it may be, with data
      return <>{children}</>;
    }
    

    Finally, if your intention is to set up a global store or context to share data between Server Components, you may reconsider your approach, as they say on the doc:

    Since Server Components are not interactive and therefore do not read from React state, you don't need the full power of context to share data. You can use native JavaScript patterns like global singletons within module scope if you have common data that multiple Server Component need to access.

    For example, a module can be used to share a database connection across multiple components:

    // utils/database.ts
    
    export const db = new DatabaseConnection();
    
    // app/users/layout.tsx
    
    import { db } from '@utils/database';
     
    export async function UsersLayout() {
      let users = await db.query();
      // ...
    }
    
    // app/users/[id]/page.tsx
    
    import { db } from '@utils/database';
     
    export async function DashboardPage() {
      let user = await db.query();
      // ...
    }
    

    In the above example, both the layout and page need to make database queries. Each of these components shares access to the database by importing the @utils/database module.

    But say you want to use the store for Server Components because you are fetching data from an API and don't want to make multiple calls. Well, if you are using fetch() (soon other libraries) to get data, Next.js will dedupe those calls and uses the cache. So you shouldn't bother normally.