I have the react client component, it’s a part of a next.js app, this component is a simple form that sends it’s formData to a next.js server action through an async function that will do the following steps:
- Set form button to be disabled & message to "Loading".
- Send formData to Next.JS via an async server action.
- Set form button to be enabled & message to error if error.
The problem seems to be that react is batching my setState calls, so in execution it’s as if step 1 doesn’t even happen, just step 2 + 3.
I can’t figure out how to circumvent this behaviour.
Here’s the source code:
"use client";
import { SendGiftAction } from "@/app/actions/SendGiftAction";
import { useState } from "react";
export default function GoldGiftForm()
{
let [interactable, SetInteractable] = useState(true);
let [message, setMessage] = useState("");
async function Execute(data: FormData)
{
//These won't change the UI
SetInteractable(false);
setMessage("Loading");
//-------------------------
//Next.JS Server action
let response = await SendGiftAction(data);
//These will change the UI
SetInteractable(true);
setMessage(response.Success ? "" : response.Error);
//------------------------
}
return (
<form action={Execute}>
<h3>Send Gold Gift</h3>
<input
name="userID"
type="text"
placeholder="User ID"
/>
<input
name="gold"
type="number"
placeholder="Gold"
/>
<button type="submit" disabled={!interactable}> Send Gift </button>
<p>{message}</p>
</form>
);
}
This is a code snippet for SendGiftAction: https://pastebin.com/YYQeG0m4
Which is a wrapper that validates data and invokes SendSystemGift:
https://pastebin.com/uD0LLEFL
Both are standard async functions really.
2
Answers
You need to understand how async call will excute in your function
This is modified code
The issue you’re encountering arises from the fact that React batches state updates inside asynchronous functions like await or promises. So, when you call SetInteractable(false) and setMessage("Loading"), React delays rendering until the await SendGiftAction(data) finishes, which makes it seem like those UI updates don’t happen before the async call completes.
To address this, you can force React to apply the first set of state changes before executing the await by flushing the state updates. One way to do this is to use flushSync from react-dom, which ensures that React immediately processes the state updates before continuing with the async logic.
Solution: Use flushSync to force immediate state updates
Here’s how you can modify your Execute function:
Explanation:
(SetInteractable(false) and setMessage("Loading")) before proceeding
with the async operation.
see the "Loading" message and the button will be disabled before the
async function starts.