experimental_useFormStatus with form-actions?
Unanswered
Polar bear posted this in #help-forum
Polar bearOP
I've seen several examples of
Server component with a dummy handler:
Client-side 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:
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 workif you want
experimental_useFormStatus you have to use "use server", a route handler won't doPolar 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?
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.
'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
Here's a CodeSandbox:
https://codesandbox.io/p/sandbox/laughing-wing-gqzj34
https://codesandbox.io/p/sandbox/laughing-wing-gqzj34
Polar bearOP
Ok, from the docs I found:
"The
"The
useFormStatus hook can only be used as a child of a form element using a Server Action."@Polar bear Another question: Why does the submit button need to be in a separate component even when the form is in a client component?
tsx
'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.
Ant
I find this interesting as well, because I'd like to disable the whole form while submitting not only the button.
@Ant I find this interesting as well, because I'd like to disable the whole form while submitting not only the button.
Polar bearOP
My use case exactly
@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