Skip to main content
Chapter 4 Server Actions and Forms

useFormStatus and Pending UI

15 min read Lesson 15 / 28

Showing Pending States During Form Submission

useFormStatus reads the pending state of a parent form. It must be used in a component that is a child of the <form> element.

SubmitButton Component

// components/submit-button.tsx
'use client';

import { useFormStatus } from 'react-dom';

interface SubmitButtonProps {
    children?: React.ReactNode;
    pendingText?: string;
    className?: string;
}

export function SubmitButton({
    children = 'Submit',
    pendingText = 'Submitting...',
    className = '',
}: SubmitButtonProps) {
    const { pending } = useFormStatus();

    return (
        <button
            type="submit"
            disabled={pending}
            className={`relative inline-flex items-center justify-center px-4 py-2
                bg-blue-600 text-white font-medium rounded-lg
                hover:bg-blue-700 disabled:opacity-60 disabled:cursor-not-allowed
                transition-all ${className}`}
        >
            {pending ? (
                <>
                    <svg
                        className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
                        fill="none"
                        viewBox="0 0 24 24"
                    >
                        <circle
                            className="opacity-25"
                            cx="12" cy="12" r="10"
                            stroke="currentColor"
                            strokeWidth="4"
                        />
                        <path
                            className="opacity-75"
                            fill="currentColor"
                            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
                        />
                    </svg>
                    {pendingText}
                </>
            ) : (
                children
            )}
        </button>
    );
}

Use it inside any form:

import { createPost } from '@/actions/posts';
import { SubmitButton } from '@/components/submit-button';

export default function NewPostPage() {
    return (
        <form action={createPost}>
            <input name="title" placeholder="Title" required />
            <textarea name="content" rows={8} required />
            <SubmitButton pendingText="Publishing...">
                Publish Post
            </SubmitButton>
        </form>
    );
}

Disabling Inputs During Submission

'use client';

import { useFormStatus } from 'react-dom';

export function FormFields() {
    const { pending } = useFormStatus();

    return (
        <>
            <input
                name="email"
                type="email"
                disabled={pending}
                className="w-full border rounded-md px-3 py-2 disabled:opacity-50"
            />
            <input
                name="password"
                type="password"
                disabled={pending}
                className="w-full border rounded-md px-3 py-2 disabled:opacity-50"
            />
        </>
    );
}

useFormStatus eliminates the need to manually pass loading state down through component trees.