Next.js Discord

Discord Forum

experimental_useFormStatus with form-actions?

Unanswered
Polar bear posted this in #help-forum
Open in Discord
Polar bearOP
I've seen several examples of experimental_useFormStatus hook that display a loading state while the server action is pending. My problem is that I can make it work with a dummy action handler with a timeout, but not with my actual route handlers that I need for the authentication flow.

Server component with a dummy handler:
import { SubmitButton } from '@/app/components/SubmitButton';

export default function LoginPage() {
  return (
    <form
      action={async () => {
        'use server';
        await new Promise((resolve) => {
          setTimeout(() => resolve(true), 2000);
        });
      }}>
      <SubmitButton />
    </form>
  );
}


Client-side submit button:
'use client';

import { experimental_useFormStatus } from 'react-dom';

export function SubmitButton() {
  const { pending } = experimental_useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'loading' : 'submit'}
    </button>
  );
}


The above example makes the submit button label change to "loading" while the action is pending, as expected. However, if I try to use actual route handlers, such as a login route handler, the button loading state no longer works:
import { SubmitButton } from '@/app/components/SubmitButton';

export default function LoginPage() {
  return (
    <form action="/auth/login" method="post">
      {/* login form fields */}
      <SubmitButton />
    </form>
  );
}

16 Replies

you are using action of post in second example and not server actions...?
Polar bearOP
That's not a "server action" then?
no its not
a server action is an async function with the "use server" directive
this is just a normal http way of doing something similar
@Polar bear I've seen several examples of `experimental_useFormStatus` hook that display a loading state while the server action is pending. My problem is that I can make it work with a dummy action handler with a timeout, but not with my actual route handlers that I need for the authentication flow. Server component with a dummy handler: tsx import { SubmitButton } from '@/app/components/SubmitButton'; export default function LoginPage() { return ( <form action={async () => { 'use server'; await new Promise((resolve) => { setTimeout(() => resolve(true), 2000); }); }}> <SubmitButton /> </form> ); } Client-side submit button: tsx 'use client'; import { experimental_useFormStatus } from 'react-dom'; export function SubmitButton() { const { pending } = experimental_useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? 'loading' : 'submit'} </button> ); } The above example makes the submit button label change to "loading" while the action is pending, as expected. However, if I try to use actual route handlers, such as a login route handler, the button loading state no longer works: tsx import { SubmitButton } from '@/app/components/SubmitButton'; export default function LoginPage() { return ( <form action="/auth/login" method="post"> {/* login form fields */} <SubmitButton /> </form> ); }
this is because, action="/auth/login" is just a normal html form action not controlled by react, while "use server" is tightly integrated to react-dom so experimental_useFormStatus could work
if you want experimental_useFormStatus you have to use "use server", a route handler won't do
Polar bearOP
Thank you for the clarification
Polar bearOP
Another question: Why does the submit button need to be in a separate component even when the form is in a client component?

'use client';

import { changeProfileName } from '../actions';
// @ts-ignore
import { experimental_useFormStatus as useFormStatus } from 'react-dom';
import { SubmitButton } from './SubmitButton';

export function ClientComponent() {
  const { pending } = useFormStatus();
  return (
    <form action={changeProfileName}>
      <input
        type="text"
        name="userName"
      />
      <button
        type="submit"
        disabled={pending}>
        {pending ? 'Loading...' : 'Change profile name'}
      </button>
    </form>
  );
}

This form executes the server action successfully, but the button state doesn't change. If I import the button from a separate component that manages the pending state, it works.
Polar bearOP
Polar bearOP
Ok, from the docs I found:
"The useFormStatus hook can only be used as a child of a form element using a Server Action."
@Polar bear Ok, from the docs I found: "The `useFormStatus` hook can only be used as a child of a form element using a Server Action."
Ant
This means that you have to create another state veriable in the parent and sync it with pending state inside children.
Polar bearOP
Maybe they designed it with server components in mind first
Polar bearOP
And assumed that you're going to need child components to use the hook in the first place