Mastering Loading Spinners in React 19 Forms Without useState

Abhay Kumar - Aug 28 - - Dev Community

In the dynamic world of React, managing user feedback during form submissions is crucial for creating smooth and intuitive user experiences. With the release of React 19, developers are provided with several hooks to handle loading states without relying on the familiar useState hook. Among these options, useActionState, useFormStatus, and useTransition stand out. This blog will explore these hooks, provide insights on their best use cases, and offer code examples to help you integrate them into your applications.

1. useActionState: The Universally Usable Hook

useActionState is a powerful hook that can be universally applied across various actions in React. It provides a pending state, which is especially useful for displaying loading indicators during asynchronous operations like form submissions.

import { useActionState } from 'react';

function MyForm() {
    const { pending } = useActionState();

    const handleSubmit = (event) => {
        event.preventDefault();
        // Perform form submission logic
    };

    return (
        <form onSubmit={handleSubmit}>
            <input type="text" name="username" placeholder="Username" />
            <button type="submit" disabled={pending}>
                {pending ? 'Submitting...' : 'Submit'}
            </button>
        </form>
    );
}

Enter fullscreen mode Exit fullscreen mode

In this example, the pending state returned by useActionState is used to conditionally render a loading indicator within the submit button. This hook is highly versatile and can be used across various components to handle loading states effectively.

2. useFormStatus: The Go-To for Form-Level Loading States

useFormStatus is designed specifically for managing the status of forms. However, it requires you to render it as a standalone component within your form, typically inside a SubmitButton component. Once abstracted, it becomes a reusable solution for displaying loading states across multiple forms without redundant code.

import { useFormStatus } from 'react';

function SubmitButton() {
    const { pending } = useFormStatus();

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

function MyForm() {
    const handleSubmit = (event) => {
        event.preventDefault();
        // Perform form submission logic
    };

    return (
        <form onSubmit={handleSubmit}>
            <input type="text" name="username" placeholder="Username" />
            <SubmitButton />
        </form>
    );
}

Enter fullscreen mode Exit fullscreen mode

Here, the SubmitButton component leverages useFormStatus to manage the loading state. This abstraction is particularly useful when you have multiple forms across your application, as it avoids repetition and keeps your code DRY (Don’t Repeat Yourself).

3. useTransition: The Low-Level Primitive

useTransition is a lower-level primitive that provides control over more complex scenarios where managing transitions between UI states is necessary. However, its usage for showing a simple loading spinner is rare and generally not recommended for straightforward form submissions.

import { useTransition } from 'react';

function MyForm() {
    let [isPending, startTransition] = useTransition();

    const handleSubmit = (event) => {
        event.preventDefault();
        startTransition(() => {
            // Perform form submission logic
        });
    };

    return (
        <form onSubmit={handleSubmit}>
            <input type="text" name="username" placeholder="Username" />
            <button type="submit" disabled={isPending}>
                {isPending ? 'Submitting...' : 'Submit'}
            </button>
        </form>
    );
}

Enter fullscreen mode Exit fullscreen mode

In this example, useTransition provides a more manual approach to managing the pending state. While it offers flexibility, it’s often overkill for simple loading indicators and is best reserved for scenarios where transitioning between complex UI states is required.

My Approach: Abstraction and Practicality

In my experience, I typically default to using a SubmitButton component that I’ve abstracted with useFormStatus. This approach ensures that the loading state is handled consistently across all forms in my application without the need to repeat myself by managing spinners individually.

However, if a specific scenario arises where the SubmitButton cannot be used, I can still rely on the pending state from useActionState to provide user feedback. This dual approach allows me to maintain flexibility and practicality, ensuring the right tool is used for the right job.

Conclusion: Choose the Right Tool for the Job

React 19 introduces several hooks that can manage loading states effectively without useState. By understanding the nuances of useActionState, useFormStatus, and useTransition, you can select the most appropriate hook for your specific use case. For most applications, abstracting a SubmitButton with useFormStatus provides a clean and reusable solution, while useActionState offers flexibility when needed. Meanwhile, useTransition remains a specialized tool for more complex scenarios.

🔗 Connect with me on LinkedIn:
Let's connect and discuss more about React, web development, and performance enhancement!

LinkedIn Profile:Abhay Kumar

React #WebDevelopment #React19 #TypeScript #Frontend

. . . . . . . . . . .