next.js: Importing server action from 'use client' component results in error when submitted.

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: linux
      Arch: x64
      Version: #202204271406~1655476786~22.04~62dd706 SMP PREEMPT Fri Jun 17 16
    Binaries:
      Node: 18.16.0
      npm: 9.5.1
      Yarn: 3.5.0
      pnpm: N/A
    Relevant packages:
      next: 13.4.0
      eslint-config-next: 13.4.0
      react: 18.2.0
      react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

No response

Link to the code that reproduces this issue

https://codesandbox.io/p/sandbox/silly-sammet-vpw55b?file=%2Fapp%2Fsome-server-action.ts&selection=[{"endColumn"%3A24%2C"endLineNumber"%3A7%2C"startColumn"%3A24%2C"startLineNumber"%3A7}]

To Reproduce

  1. Go to codesandbox
  2. Click submit button
  3. Observe error

image

Describe the Bug

Having a component that has the directive 'use client', which then imports an action that has the directive 'use server', will result in an error stating that

Invariant: Method expects to have requestAsyncStorage, none available

When invoked from a form.

image

Expected Behavior

I expect the server action to work as expected, with both headers() and cookies() working.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 24
  • Comments: 44 (11 by maintainers)

Commits related to this issue

Most upvoted comments

@shuding I installed that version and am still seeing the error when trying to write cookies

I was able to set up a codesandbox in which it works as expected: https://codesandbox.io/p/sandbox/awesome-johnson-o9i04r?file=%2Fapp%2Faction.ts

However: When using next-auth sadly this still breaks. In next-auth they do

    const {
      headers,
      cookies
    } = require("next/headers");

    req = {
      headers: Object.fromEntries(headers()), // same error here
      cookies: Object.fromEntries(cookies().getAll().map(c => [c.name, c.value]))
};

which for some reason does not. I will open an issue in their repo, referencing this.

I have the same issue with Clerk.

import { currentUser } from "@clerk/nextjs/app-beta";

This should be fixed with #49470, a new canary will be cut soon.

Still hitting this issue with canary.4 and Clerk auth.

Having the same problem, which makes it so that next auth getServerSessions doesn’t work. There is a potential workaround for the time being but it’s really not nice: Instead of importing the function in the client component you can import it in a server component and pass it as a prop to the client component. Then the error goes away. But aside from the boilerplate that kind of defeats the purpose of it.

@borispoehland As described in the following document.

To call Server Actions that perform authentication internally from the Client Component, the header must be accessible. Therefore, import Server Actions in the parent Server Component and pass them to the target Client Component by prop drilling. Direct import of Server Actions from the Client Component will result in an error.

https://clerk.com/docs/nextjs/server-actions#with-client-components

you are returning a Promise. If you changes to code like this, it will be ok. …startTransition(() => { createUser(data) })

you can’t use async function with startTransition. And, the server action must be async function.

This is incorrect. startTransition supports async transitions when you have server actions enabled (using experimental React).

E.g. this is valid:

const [data, setData] = useState(null)
const [isPending, startTransition] = useTransition()
startTransition(async () => {
  const res = await fetch('/my-api')
  const json = await res.json()
  setData(json)
})

If you want to call next-auth’s getServerSession in a server action from a client component, I posted a workaround here that you can use until the bug is fixed.

Should be good in 13.4.2-canary.2!

Same error “Invariant: Method expects to have requestAsyncStorage, none available” when access cookies in server actions which called by client component (triggered by onClick with startTransition). Access cookies in server actions used by server component (such as <form action={}) works fine.

@borispoehland As described in the following document. To call Server Actions that perform authentication internally from the Client Component, the header must be accessible. Therefore, import Server Actions in the parent Server Component and pass them to the target Client Component by prop drilling. Direct import of Server Actions from the Client Component will result in an error. https://clerk.com/docs/nextjs/server-actions#with-client-components

That fixed it for me, thnx ♥️

For me too, thanks @EringiV3! However, I find it highly inconvenient to pass server actions via prop drilling. In some cases I had to pass it down 4 layers. I think in this case it‘d be more clean to just create an API route, do you agree? 😄

@borispoehland As described in the following document.

To call Server Actions that perform authentication internally from the Client Component, the header must be accessible. Therefore, import Server Actions in the parent Server Component and pass them to the target Client Component by prop drilling. Direct import of Server Actions from the Client Component will result in an error.

https://clerk.com/docs/nextjs/server-actions#with-client-components

That fixed it for me, thnx ♥️

Still hitting this issue with canary.4 and Clerk auth.

Yep, in Clerk it’s because of this:

const buildRequestLike = () => {
    try {
        // Dynamically import next/headers, otherwise Next12 apps will break
        // because next/headers was introduced in next@13
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const { headers } = require('next/headers');
        return new server_1.NextRequest('https://placeholder.com', { headers: headers() });
    }
    catch (e) {
        if (e &&
            'message' in e &&
            typeof e.message === 'string' &&
            e.message.toLowerCase().includes('Dynamic server usage'.toLowerCase())) {
            throw e;
        }
        throw new Error(`Clerk: auth() and currentUser() are only supported in App Router (/app directory).\nIf you're using /pages, try getAuth() instead.\nOriginal error: ${e}`);
    }
};

Weirdly when I put this code inside of my server action directly:

const { headers } = require('next/headers');
headers()

it works

Does anyone know why it still ain’t working with Clerk.js?

Still causing in 13.4.2-canary.3

Ran into this as well, but the workaround didn’t seem to fix it for me

Passing the server action by action to a client component as described here: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#props

Works flawlessly. 👍