This is the start of my React built-in hooks series, where I took some time to look at the React docs and have a deeper look into each respective built-in hook, even the ones I'm already very familiar with.
useActionState
has some interesting use cases where it provides a way to store your state along with a loading transition for the action callback you pass for the form to trigger upon submission.
What this means is that by using a server function to grab your form input values by their unique name attributes, you're able to tell the code to wait for the form submission action to be completed before you're able to see the updated data.
Here is my demo, where you can see it in action (hehe). The code informs the user that it's currently loading until the form submission is complete. You can see this demonstration with the failed request in a delayed time to observe it in action and how quickly it runs without delay.
app/page.tsx
"use client"
import React, {useActionState} from "react";
import {addToCart} from "./hooks/addToCart";
export interface ICart {
itemID: string,
itemTitle: string,
itemPrice: number,
itemQuantity: number
}
export const cart: ICart[] = [];
function AddToCartForm({itemTitle, itemID, itemPrice}:{itemTitle: string, itemID: string, itemPrice: number}) {
const [cartItem, cartAction, isPendingCart] = useActionState(addToCart, {itemTitle: "", cartID: "", itemPrice: 0, itemQuantity: 0});
return (
<form action = {cartAction}>
<h2>{itemTitle} ${itemPrice}</h2>
<input type="hidden" name="itemID" value={itemID} />
<input type="hidden" name="itemTitle" value={itemTitle} />
<input type="hidden" name="itemPrice" value={itemPrice} />
<label>Add number of items to cart: </label><input type="number" name="itemQuantity"/>
<button type="submit">Add to Cart</button>
<span>{cartItem.message}</span>
{isPendingCart ? "Loading..." : <section>
<h2>{cartItem.itemTitle}</h2>
<h4>{cartItem.total}</h4>
</section>}
</form>
);
}
export default function Home(){
return(
<main>
<AddToCartForm itemTitle={"Hatsune Miku shirt"} itemID = {"1"} itemPrice={12.12}/>
</main>
)
}
app/hooks/addToCart.ts
"use server"
export async function addToCart(prevState, queryData){
const itemID:string = queryData.get('itemID');
const itemTitle:string = queryData.get('itemTitle');
const itemPrice:number = queryData.get('itemPrice');
const itemQuantity:number = queryData.get('itemQuantity');
const message = "Item was added to cart!"
if (itemID && itemTitle && itemPrice && itemQuantity > 0) {
const total = itemPrice * itemQuantity
return {itemID, itemTitle, itemPrice, itemQuantity, message, total};
} else {
// Add a fake delay to make waiting noticeable.
await new Promise(resolve => {
setTimeout(resolve, 2000);
});
const total = itemPrice * itemQuantity
const message = "Enter a valid quantity!"
return {itemID, itemTitle, itemPrice, itemQuantity, message, total};
}
}
My hope with this series is to bring underrated built-in hooks to light, as well as highlight some things you may not have known about familiar hooks.
Top comments (10)
Really enjoying this deep dive into underrated hooks, useActionState's loading feedback is something I've overlooked in my own forms. Are you planning to cover useOptimistic or any other newer ones next?
Glad to hear that!! Indeed, I plan on covering all of them hahhahaha
Love when someone actually goes deep on the less flashy hooks - makes me want to poke around with useActionState more myself.
You definitely should experiment around useActionState more!!! It was fun using it and learning more about it.
useActionState is a handy hook for managing state updates based on user actions, especially in forms. It's a great addition for cleaner and more predictable state transitions.
For sure!!! I love how much more clean my code looks~
I have a twitter or linkedin if you want to chat :)
Thank you!! Likewise ^^