Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Importing server action from 'use client' component results in error when submitted. #49235

Closed
1 task done
karl-run opened this issue May 4, 2023 · 44 comments
Closed
1 task done
Labels
bug Issue was opened via the bug report template. locked

Comments

@karl-run
Copy link

karl-run commented May 4, 2023

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=%5B%7B%22endColumn%22%3A24%2C%22endLineNumber%22%3A7%2C%22startColumn%22%3A24%2C%22startLineNumber%22%3A7%7D%5D

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

@karl-run karl-run added the bug Issue was opened via the bug report template. label May 4, 2023
@karl-run
Copy link
Author

karl-run commented May 4, 2023

Should work as described in the new docs here: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#import 😄

@karl-run
Copy link
Author

karl-run commented May 4, 2023

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. 👍

@IanMitchell
Copy link
Contributor

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

@mauricekleine
Copy link

mauricekleine commented May 5, 2023

I'm getting the same error, but only when I try to get the headers imported from next/headers (import { headers } from next/headers)

Screenshot 2023-05-05 at 11 12 24

@timootten
Copy link

I get the same error with cookies() (next-auth)

@soylemezali42
Copy link

Same issue when reading cookies in a server file.

@Rykuno
Copy link

Rykuno commented May 7, 2023

+1. Cannot access cookies when calling a server function from a client component

@TimBroddin
Copy link

I have the same issue with Clerk.

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

@GomaGoma676
Copy link

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
Copy link

+1

@Escapado
Copy link

Escapado commented May 7, 2023

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.

@shuding
Copy link
Member

shuding commented May 8, 2023

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

@shuding
Copy link
Member

shuding commented May 8, 2023

Should be good in 13.4.2-canary.2!

@shuding shuding closed this as completed May 8, 2023
@IanMitchell
Copy link
Contributor

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

shuding added a commit that referenced this issue May 8, 2023
@shuding
Copy link
Member

shuding commented May 8, 2023

I added a test case in #49488 to ensure it works. Could you create a CodeSandbox reproduction with 13.4.2-canary.2?

@IanMitchell
Copy link
Contributor

Repro here: https://codesandbox.io/p/sandbox/happy-elgamal-6fp5rg

First time using codesandbox, let me know if I can change anything to better help!

@shuding
Copy link
Member

shuding commented May 8, 2023

Thanks for the repro! It's strange that although you have 13.4.2-canary.2 in package.json, it's still 13.4.0 running:

CleanShot 2023-05-09 at 01 03 12@2x

Maybe try a clean installation?

@IanMitchell
Copy link
Contributor

Hm yeah, updating that does seem to make it work. In my actual app, I'm still seeing the error but I can't seem to get a repro case going on. I did notice that there is a new log before the error happens:

Failed to generate cache key for https://bsky.social/xrpc/com.atproto.server.createSession

Will keep trying to create a repro

@Escapado
Copy link

Escapado commented May 9, 2023

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.

timneutkens pushed a commit that referenced this issue May 9, 2023
This PR adds a test case for `headers()` and `cookies().set()` in Client
Component imported actions.
@Rykuno
Copy link

Rykuno commented May 9, 2023

nextauthjs/next-auth#7486

Here is the issue created at Next-Auth now that its confirmed 13.4.2-canary.2 solved the cookies/headers issue.

@dclark27
Copy link

dclark27 commented May 10, 2023

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

@borispoehland
Copy link

borispoehland commented May 11, 2023

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

@ludwigbacklund
Copy link

ludwigbacklund commented May 11, 2023

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.

@PandelisZ
Copy link

Are y'all using

export const runtime = 'edge' ?

that seemed to affect me

@KeisukeNagakawa
Copy link

Still causing in 13.4.2-canary.3

@KeisukeNagakawa
Copy link

Still causing in 13.4.2-canary.4

My codes:
client side component

        <button
          className="btn-primary btn"
          onClick={() => startTransition(async () => submitArgument(transcript))}
        >
          Submit
        </button>

server action

"use server"
import { currentUser } from "@clerk/nextjs";
...
export function X (){
 ...
 const user = await currentUser()
 ...
}
...

@soylemezali42
Copy link

soylemezali42 commented May 23, 2023

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

@borispoehland
Copy link

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

But server actions must be async, no?

@KeisukeNagakawa
Copy link

@soylemezali42 Apologies, you are right. startTransition accepts only non-async function though server action should be written as aysnc. Thank you.

@borispoehland My fixed code is this. Have your self try.

        <button
          className="btn-primary btn"
          onClick={() => startTransition(() => submitArgument(transcript))}
        >
          Submit
        </button>
"use server"
import { currentUser } from "@clerk/nextjs";
...
export async function X (){
 ...
 const user = await currentUser()
 ...
}
...

@FrimJo
Copy link

FrimJo commented May 24, 2023

I'm facing the same problem where I'm using react-hook-form and the onSubmit prop on <form>.

// page.tsx

"use client"
import { createUser } from "./actions";

export default function CreateUser() {
    const { register, handleSubmit } = useForm();
    const [ startTransition ] = useTransition();

    return (
        <form onSubmit={handleSubmit((data) => startTransition(() => createUser(data)))}>
            ...
        </form>
    )
}
// actions.ts

"use server"
import { currentUser } from "@clerk/nextjs";
import { CreateUserFormData } from "@/lib/create-user-form";

export async function createUser(data: CreateUserFormData) {
    const clerkUser = await currentUser();
    ....
}
Error: Clerk: auth() and currentUser() are only supported in App Router (/app directory).
If you're using /pages, try getAuth() instead.
Original error: Error: Invariant: Method expects to have requestAsyncStorage, none available

@soylemezali42
Copy link

soylemezali42 commented May 24, 2023

First, you use the useTransition hooks wrong. Implementation will be like this useTransition, also you can use startTransition without pending. I think it will be more accurate for your usecase startTransition

@FrimJo

...startTransition(() => createUser(data))

you are returning a Promise. If you changes to code like this, it will be ok.

...startTransition(() => {
   createUser(data)
})

@timneutkens
Copy link
Member

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)
})

@timneutkens
Copy link
Member

If you're running into a similar issue please open a new issue with a reproduction, thank you!

@soylemezali42
Copy link

soylemezali42 commented May 24, 2023

I'm sorry for misleading you. but I made my comment according to the official react document. This part of the document also needs to be changed.startTransition ceveats

Screenshot 2023-05-24 at 22 41 11

@FrimJo
Copy link

FrimJo commented May 24, 2023

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)
})

So the code I wrote in #49235 (comment) is correct and should work? I tried not returning promise as proposed by @soylemezali42 to no avail.

If you're running into a similar issue please open a new issue with a reproduction, thank you!

You wan't me to create separate issue? Because the error still persists.

@borispoehland
Copy link

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?

@EringiV3
Copy link

@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

@FrimJo
Copy link

FrimJo commented May 28, 2023

@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 ♥️

@borispoehland
Copy link

@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? 😄

@KeisukeNagakawa
Copy link

@FrimJo @EringiV3 Hi, could you share the code exmaple for prop driling when using Clerk? Thanks!

@EringiV3
Copy link

EringiV3 commented Jun 2, 2023

@KeisukeNagakawa
Here is my sample application repository.
https://github.com/EringiV3/app-router-tweet-app/blob/main/src/app/(application)/layout.tsx

src/app/(application)/layout.tsx is the Server Component and prop drilling to the target Client Component, TweetModal.tsx.

@KeisukeNagakawa
Copy link

@EringiV3 Thanks!

@codinginflow
Copy link

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. 👍

It works for me too but I get a lint warning that the prop is not serializable. How did you define the type for the server action prop?

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template. locked
Projects
None yet
Development

No branches or pull requests