next.js: [NEXT-1168] useFormStatus doesn't seem to work with server actions

Verify canary release

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

Provide environment information

Binaries:
      Node: 20.0.0
      npm: 9.6.4
      Yarn: 1.22.17
      pnpm: 7.27.0
    Relevant packages:
      next: 13.4.0
      eslint-config-next: N/A
      react: 0.0.0-experimental-aef7ce554-20230503
      react-dom: 0.0.0-experimental-aef7ce554-20230503

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

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/rliang/next-use-form-status-bug-reproduction

To Reproduce

  • run npm run dev
  • open localhost:3000 in the browser
  • click on the submit button

Describe the Bug

useFormStatus doesn’t work, such that its return value is never updated when submitting a form.

Expected Behavior

The page should update the formStatus after clicking on submit.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1168

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 15
  • Comments: 52 (2 by maintainers)

Commits related to this issue

Most upvoted comments

Found out that useFormStatus in fact looks up the status of the nearest <form> parent element, i.e. the element that uses this hook must be a child of a <form>.

Still getting the error in the server console though, even after upgrading next/react/react-dom.

So… what is the recommended way of showing a loading status for server-component form submissions?

The only solution that has worked for me is having the form and server action in a server component, then have a client component inside the form (should be a child of the form) consuming useFormStatus.

You can find a full working example here: https://github.com/techulus/manage-prompt/blob/c9dad49a802c2192d1553d62e4b3151dda134fdf/app/console/workflows/new/page.tsx#L7

I got a TS error when trying to import:

import { experimental_useFormStatus as useFormStatus } from "react-dom";

When ignoring this error, problem seems to be solved

// ts-ignore because experimental_useFormStatus is not in the types
// @ts-ignore
import { experimental_useFormStatus as useFormStatus } from "react-dom";

I just updated to next.js 14. I had to add @ts-expect-error

// @ts-expect-error
import { useFormStatus } from 'react-dom';

else typescript throws an error that useFormStatus is not exported . It works fine for me now though.

Just hit this too

Moreover, the terminal also logs an error:

error - TypeError: (0 , react_dom__WEBPACK_IMPORTED_MODULE_1__.experimental_useFormStatus) is not a function

Even though the initial state is correctly displayed in the browser.

Hi, the documentation needs to be updated in nextjs. They even did not mention this experimental stuff. Also i’m kinda start get frustrated these days. I’m currently in a migration to nextjs with my pet project and keep hit these kind of walls. Started with prisma and edge runtime. I know this is not easy and i’m really appriciate the work behind this meta framework but this server side action things needs to be clearified. Most of these kind of frameworks already can provide a single loading state from server side action. I think the workaround maybe the startTransition hook but i don’t know because this is very under documented. Sorry for this but i’m overall kinda frustrated the current frontend era…

Just in case if anyone is getting caught on the part where return value is not updated for a submitted form, I wrote a small utility that could help you with that. It allows you to lift the state of useFormStatus from child component up to the parent.

https://gist.github.com/JLarky/190bab52ff13c44f9420523d1792fbf0

So… what is the recommended way of showing a loading status for server-component form submissions?

In next.config.js enable server actions.Don’t forget to use npm run dev to build the project again. This worked for me.

const nextConfig = { experimental:{ serverActions:true, }, }

This does work in NextJS 13.5.6, but now seems to be broken in NextJS 14.0.0.

Anyone else having this issue?

I got a TS error when trying to import:

import { experimental_useFormStatus as useFormStatus } from "react-dom";

When ignoring this error, problem seems to be solved

// ts-ignore because experimental_useFormStatus is not in the types
// @ts-ignore
import { experimental_useFormStatus as useFormStatus } from "react-dom";

But while deploying in vercel I will throw same error. Locally it runs despite of typescript error.

Does anyone knows how to make this work with jest+testing library and with storybook, in both integration and visual tests i am getting (0 , react_dom__WEBPACK_IMPORTED_MODULE_1__.experimental_useFormState) is not a function

Ignore me… Apologies…

I was missing my morning coffee and not reading error responses… and on my second attempt see it’s working.

Our code for NextJS 13.5.6 works in NextJS 14.0.0, with the only change being to remove the ‘experimental_’ prefix within the imports (both for useFormState and useFormStatus).

Has anyone found a way to get around that ?

what if I want to have

export default function Contact() {
  const [state, formAction] = useFormState(myAction, initialState);
  const { pending } = useFormStatus();
  
  return <div>
    {pending ? <h1>Loading...<h1> : ''}
    <div>
      ...
        <form action={formAction}>
        ....

@panoskouff useFormStatus can only be used inside a client component, which is a child of a <form>. So what you can do is create a separate loading client component and place that inside your form.

"use client"

export default function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" disabled={pending}>{pending ? 'Loading...' : 'Submit'}</button> 
  )
}
export default function Contact() {
  return (
    <form action={formAction}>
       ...
       <SubmitButton />
    </form>
  )
}

If you don’t want anything to be visible from the form, you could make all your inputs part of a client component.

"use client"

export default FormInputs() {
  const { pending } = useFormStatus()

  return (
    <>
      {pending ? (
        <h1>Loading...<h1>
      ) : (
         <>
           <label><input /></label>
           ...
         </>
      )}
    </>
  )
}

Edit

I managed to get rid of the experimental_useFormStatus is not a function error with the following versions:

{
    "react": "experimental",
    "react-dom": "experimental",
    "next": "canary"
}

But it’s only for client components. So I get that error on server components.

You're importing a component that needs experimental_useFormStatus. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.


Original

@mauricekleine

How did you manage to get rid of experimental_useFormStatus is not a function exactly? The following way?

{
    "react": "next",
    "react-dom": "next",
    "next": "canary"
}

After upgrading to Next 14 (14.0.4 to be exact), I could import useFormStatus (the stable one).

@kuijpie thank you 🙏🏻

the only thing that helped me is to place action and form tag in a Server component, but the form content in JSX.fragment inside Client component.

I updated NextJS 13.5.5 to 13.5.7, it worked for me. Just run npm i next@canary

how was this problem solved im still very much confused

I got a TS error when trying to import:

import { experimental_useFormStatus as useFormStatus } from "react-dom";

When ignoring this error, problem seems to be solved

// ts-ignore because experimental_useFormStatus is not in the types
// @ts-ignore
import { experimental_useFormStatus as useFormStatus } from "react-dom";

But while deploying in vercel I will throw same error. Locally it runs despite of typescript error.

Above to work locally and on vercel, have it like this:

// @ts-expect-error
import { experimental_useFormStatus as useFormStatus } from "react-dom";

I have tried a lot of different ways, but the API is definitely experimental and breaks. So, reverted back to not using form action for complex form processing.

Just in case if anyone is getting caught on the part where return value is not updated for a submitted form, I wrote a small utility that could help you with that. It allows you to lift the state of useFormStatus from child component up to the parent.

https://gist.github.com/JLarky/190bab52ff13c44f9420523d1792fbf0

Thank you! I appreciate the gist – it worked great.

any idea why the pending flag does not update when the form is submitting? It’s always false.

@Gregoor You found a way to retrieve the response from the server action? I want to receive if had an error on server-side, and display that on my client component.

@Gregoor Yeah that works because you’re using it in a client component. Documentation doesn’t state that:

image