Mar 12, 2025

    useactionstate hook in react | everything you need to know

    Overview

    useActionState is a hook in react that enables us to update state based on the result of a form action. When using this hook we don't have to keep track of the state of our form's return value ourselves., the hook will take care of it.

    When used with only react, the form action's return value is stored as local state, but when used with a framework which supports server components (like NextJS), we can make our form interactive even before the web page is hydrated. We will delve deeper into both these aspects of the hook later in this blog. For now let's focus on the basic functionality of the hook.

    The hook has the following signature :

    const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);
    

    the hook takes three arguments:

    1. A function that we want to act as our form action, this will be called whenever our form is submitted. It is important to note that the first argument to this function will be the previous state of the form (which will be our initialState when the action is executed for the first time), So the hook will modify the signature of our action function, i.e. first argument is the previous state of the form, followed by the usual arguments that a form action receives.
    2. The value that we want the state to be initially, this will be ignored after the action is executed for the first time.
    3. a unique URL for the page that this form modifies, Useful on pages with dynamic content (eg: Shopping Cart, if fn is a server function and the form is submitted before the page is hydrated, the browser will navigate to the specified permalink URL, instead of the current page's url. We need to make sure that the same form is rendered on the destination page with the same action fn and permalink, if the form has been hydrated this argument will not have any efffect.

    The hook return three values :

    1. the current state, initially it will be equal to our initialState that we passed to the hook, After the action is invoked once, it will be equal to the value returned by the action.
    2. A new action that we have to pass to our form as it's action or formAction to any button within the form. Alternatively we can use startTransition to manually invoke this action (not covered here)
    3. isPending flag to track the form status for any pending transition.

    Let's dive deeper with some examples.

    Example 1: Display Form Errors

    We can use this hook to display errors for the form. For this example we have a simple react app to show a login form, and an action. We are simulating a delay in the action for incorrect values to show loading state.

    login-form.tsx

    import { useActionState } from "react";
    import { login } from "../actions";
    
    const LoginForm = () => {
      const [message, formAction, isPending] = useActionState(login, "");
      return (
        <form
          action={formAction}
          className="bg-sky-100 max-w-md mx-auto rounded-sm p-4"
        >
          <h2>NestedBlock: Login</h2>
          <input
            type="email"
            name="email"
            placeholder="email"
            className="mt-4 w-3/4 text-gray-100 bg-slate-600 p-2"
          />
          <div className="mt-4">
            <button className="bg-green-600 w-1/2 text-white px-3 py-1 rounded-sm">
              Login
            </button>
          </div>
          {isPending && <p className="mt-2">Loading...</p>}
          {message && <p className="mt-2">{message}</p>}
        </form>
      );
    };
    
    export default LoginForm;
    
    

    actions.ts

    export const login = async (_:string,queryData:FormData) => {
        const email = queryData.get('email')
        if(email === 'me@nestedblock.com'){
            return "Successfully Logged In"
        }
        else {
            await new Promise(resolve => {
                setTimeout(resolve,2000)
            })
    
            return "Couldn't Login Incorrect Email"
        }
    }
    

    App.tsx

    import LoginForm from "./components/login-form";
    
    function App() {
      return (
        <>
          <div className="min-h-screen w-full bg-slate-800 py-32 text-center ">
              <LoginForm/>
          </div>
        </>
      );
    }
    
    export default App;
    
    

    This is the final result :


    Example 2 : Structured Info After Submiting A Form

    For this example we will create another form to simulate adding items to a shopping cart and create another action for it.

    add-to-cart.tsx

    import { useActionState } from "react";
    import { addToCart } from "../actions";
    
    const initialState = {
        success:true,
        message:'',
        cartSize:0
    }
    
    const AddToCartForm = () => {
    
        const [formState, formAction] = useActionState(addToCart, initialState);
      return (
        <form
        action={formAction}
        className="bg-sky-100 max-w-md mx-auto rounded-sm p-4"
      >
        <h2>NestedBlock: Add To Cart</h2>
        <div className="mt-4">
        <input
            type="text"
            name="itemName"
            placeholder="Item Name"
            className="mt-4 w-3/4 text-gray-100 bg-slate-600 p-2"
          />
          <button className="mt-4 bg-green-600 w-1/2 text-white px-3 py-1 rounded-sm">
            Add To Cart
          </button>
        </div>
        {formState.success  && <p className="mt-2">Item added to cart.</p>}
        {!formState.success && formState.message && <p className="mt-2">{formState.message}</p>}
      </form>
      )
    }
    
    export default AddToCartForm
    

    action.ts

    export const addToCart = (prevState:{
        success:boolean,
        message:string
    },queryData:FormData) => {
        const itemName = queryData.get('itemName');
        if (itemName === "shoes") {
          return {
            success: true,
            message:"",
            cartSize: 12,
          };
        } else {
          return {
            success: false,
            message: "The item is not in stock.",
          };
        }
    }
    

    Notice we are now returning an object instead of a plain string from the action, this enables us to do things like returning the success flag along with the message, You can also modify it to suit your needs ( eg. you can return an array of objects for all form fields with an error flag and a message to track each field individually ). Here is the result :

    Share this:

    Category:

    frontend development
    0/3000