React's useActionState Explained with a Real-World Example

useActionState is a React hook that simplifies managing form submissions and their state. It’s particularly useful in React applications (especially with frameworks like Next.js) that use server actions or progressive enhancement. It allows you to:

  • Track form state: Keep track of the result of a form submission (e.g., success or error messages).

  • Handle form actions: Wrap an existing action (like a server function) to manage form submissions.

  • Show loading states: Provide feedback (e.g., "Loading...") while the action is processing.

    Example: Create Account Form

    Here’s a simple form that uses useActionState to validate and submit user input, show relevant errors, and reset the form on success.

      const [state, formAction, isPending] = useActionState(submitForm, initialState);
    
    • state: Current status of the form (success/error).

    • formAction: Hook-enhanced action used as the form's action.

    • isPending: Boolean to show a loading state during submission.

Upon submission:

  • To simulate a backend check for existing users:

    • If the name is "john007" or the email is "john@gmail.com", the form returns a "user already exists" error.

    • On successful submission, a thank-you message is displayed and the form is reset

Profile.js

import { useState, useEffect } from "react";
import { useActionState } from "react";
import { submitForm } from "./utils";

const initialState = {
  status: "",
  errorField: "",
  message: "",
};

export const FeedbackForm = () => {
  const [inputName, setInputName] = useState("");
  const [inputEmail, setInputEmail] = useState("");

  const [state, formAction, isPending] = useActionState(
    submitForm,
    initialState
  );

  useEffect(() => {
    if (state.status === "success") {
      setInputName("");
      setInputEmail("");
    }
  }, [state.status]);

  // Validate form to enable/disable submit button
  const isFormValid = inputName.trim() !== "" && inputEmail.trim() !== "";

  return (
    <div className="feedback-form-container">
      <h2 className="feedback-form-title">Create Your Account</h2>
      <form action={formAction} className="feedback-form">
        <div className="form-group">
          <label htmlFor="name" className="form-label">
            Name
          </label>
          <input
            type="text"
            name="name"
            id="name"
            className="form-input"
            placeholder="Your name"
            value={inputName}
            onChange={(e) => setInputName(e.target.value)}
            disabled={isPending}
          />
          {state.status === "error" && state.errorField === "name" && (
            <p className="form-error">{state.message}</p>
          )}
        </div>
        <div className="form-group">
          <label htmlFor="email" className="form-label">
            Email
          </label>
          <input
            type="email"
            name="email"
            id="email"
            className="form-input"
            placeholder="Your email"
            value={inputEmail}
            onChange={(e) => setInputEmail(e.target.value)}
            disabled={isPending}
          />
          {state.status === "error" && state.errorField === "email" && (
            <p className="form-error">{state.message}</p>
          )}
        </div>

        <button
          type="submit"
          className={`form-button ${
            isPending || !isFormValid ? "button-disabled" : ""
          }`}
          disabled={isPending || !isFormValid}
        >
          {isPending ? "Creating..." : "Create Account"}
        </button>

        {state.status === "success" && (
          <p className="form-success">{state.message}</p>
        )}
      </form>
    </div>
  );
};

utils/submitFeedback

export const submitForm = async (prevState, formData) => {
  const name = formData.get("name")?.trim();
  const email = formData.get("email")?.trim();

  await new Promise((res) => setTimeout(res, 1000));

  if (name === "john007") {
    return {
      status: "error",
      errorField: "name",
      message: "username already exist",
    };
  }

  if (email === "john@gmail.com") {
    return {
      status: "error",
      errorField: "email",
      message: "Email already exist",
    };
  }

  return {
    status: "success",
    message: `Thanks, ${name}! Your Account has been created.`,
  };
};

Output

10
Subscribe to my newsletter

Read articles from AKhil kumar Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

AKhil kumar Singh
AKhil kumar Singh