React.js useState does not update the UI as expected
Answered
Rex posted this in #help-forum
RexOP
Hello, I have this problem in react.js where I want the UI to be updated after each
setBlocks call, but currently it is updating only after the last call of setBlocks, here is my code:const handleQuery = async (formData: FormData) => {
const jsonTable = JSON.stringify(table.toJson())
const query = formData.get("query") as string
const newBlock = new Block("Query", BlockType.Chat, query)
setBlocks([...blocks, newBlock])
formRef.current?.reset()
const modelResponse = await getAIResponse(query, jsonTable)
setBlocks([...blocks, new Block("Bot", BlockType.Chat, modelResponse)])
}47 Replies
Plott Hound
In React, state updates may be asynchronous and batched for performance optimization. This means when you call setBlocks multiple times in quick succession, React may batch these updates together and re-render only once.
Cimarrón Uruguayo
why not just do
setBlocks([...blocks, newBlock, modelResponseBlock])?instead of two
setBlocks calls@Cimarrón Uruguayo why not just do `setBlocks([...blocks, newBlock, modelResponseBlock])`?
RexOP
The UI should be updated two seperate times, the addition of each block should be reflected to the UI
The
modelResponseBlock take a long time to be calculatedSo the UI should first be updated with the
newBlock, then when the modelResponseBlock is ready it should be updated again.@Rex So the UI should first be updated with the `newBlock`, then when the `modelResponseBlock` is ready it should be updated again.
try this
import { flushSync } from 'react-dom';
const handleQuery = async (formData: FormData) => {
const jsonTable = JSON.stringify(table.toJson())
const query = formData.get("query") as string
const newBlock = new Block("Query", BlockType.Chat, query)
flushSync(() => {
setBlocks([...blocks, newBlock])
}
formRef.current?.reset()
const modelResponse = await getAIResponse(query, jsonTable)
setBlocks([...blocks, new Block("Bot", BlockType.Chat, modelResponse)])
}RexOP
OK this does work,
can you explain what
can you explain what
flushSync does ?@Rex OK this does work,
can you explain what `flushSync` does ?
it tell react to force update the state instead of waiting the asynchronously
RexOP
Also I see that the docs saying
Using flushSync is uncommon and can hurt the performance of your app. so is it the only way to achieve this ?@Ray yes or change the logic of the code
RexOP
Ok, do have an idea of what changes can be better ?
@Rex Ok, do have an idea of what changes can be better ?
what are you trying to do?
RexOP
So basically this is a chatbot interface, each message is represented by a Block
When the user types a message which is a string, I create a Block object that should then be rendered to the UI,
This message is then sent to an AI engine to generate a response (this takes a long time), after the response is generated, I create Block object from it then display it to the user
When the user types a message which is a string, I create a Block object that should then be rendered to the UI,
This message is then sent to an AI engine to generate a response (this takes a long time), after the response is generated, I create Block object from it then display it to the user
@Rex So basically this is a chatbot interface, each message is represented by a Block
When the user types a message which is a string, I create a Block object that should then be rendered to the UI,
This message is then sent to an AI engine to generate a response (this takes a long time), after the response is generated, I create Block object from it then display it to the user
could you try this
const handleAddMessage = (formData: FormData) => {
const jsonTable = JSON.stringify(table.toJson())
const query = formData.get("query") as string
const newBlock = new Block("Query", BlockType.Chat, query)
setBlocks([...blocks, newBlock])
formRef.current?.reset()
handleQuery(query,j sonTable)
}
const handleQuery = async () => {
const modelResponse = await getAIResponse(query, jsonTable)
setBlocks([...blocks, new Block("Bot", BlockType.Chat, modelResponse)])
}RexOP
With this, the UI only shows one new block that is coming from
handleQueryThe first block generated by
handleAddMessage is never shownI tried
setBlocks((current) => [...current, newBlock]) but that doesn't fix it@Rex The first block generated by `handleAddMessage` is never shown
yes use
handleAddMessage when the user type message@Ray yes use `handleAddMessage` when the user type message
RexOP
Yeah I did this
I call it when the user press the submit button
From the browser console I can see that both functions are called at the correct time, but for some reason the UI isn't updating correctly
ok i got it
@Rex Yeah I did this
const handleAddMessage = (formData: FormData) => {
const jsonTable = JSON.stringify(table.toJson())
const query = formData.get("query") as string
const newBlock = new Block("Query", BlockType.Chat, query)
setBlocks(b => [...b, newBlock])
formRef.current?.reset()
handleQuery(query,j sonTable)
}
const handleQuery = async () => {
const modelResponse = await getAIResponse(query, jsonTable)
setBlocks(b => [...b, new Block("Bot", BlockType.Chat, modelResponse)])
}try again with this
RexOP
Now we are back at the first issue of the UI only be updated after the second call to
setBlocks@Rex Now we are back at the first issue of the UI only be updated after the second call to `setBlocks`
not sure how your rest the code look like but I just tested with this code and it seem to be working
RexOP
Can you send me the code you tested with ?
Plott Hound
I’m away from the pc but would something like this work?
const handleQuery = async (formData: FormData) => {
const jsonTable = JSON.stringify(table.toJson())
const query = formData.get("query") as string
const newBlock = new Block("Query", BlockType.Chat, query)
// First update
setBlocks(prevBlocks => [...prevBlocks, newBlock])
formRef.current?.reset()
const modelResponse = await getAIResponse(query, jsonTable)
// Second update
setBlocks(prevBlocks => [...prevBlocks, new Block("Bot", BlockType.Chat, modelResponse)])
}
Sorry for formatting
const handleQuery = async (formData: FormData) => {
const jsonTable = JSON.stringify(table.toJson())
const query = formData.get("query") as string
const newBlock = new Block("Query", BlockType.Chat, query)
// First update
setBlocks(prevBlocks => [...prevBlocks, newBlock])
formRef.current?.reset()
const modelResponse = await getAIResponse(query, jsonTable)
// Second update
setBlocks(prevBlocks => [...prevBlocks, new Block("Bot", BlockType.Chat, modelResponse)])
}
Sorry for formatting
@Rex Can you send me the code you tested with ?
import { MouseEvent, useState } from "react";
class Block {
constructor(id: string, text: string) {
this.id = id;
this.text = text;
}
id: string;
text: string;
}
export default function Page() {
const [blocks, setBlocks] = useState<Block[]>([]);
const handleClick = async (e: MouseEvent<HTMLButtonElement>) => {
setBlocks((b) => [...b, new Block(Math.random().toString(), "1")]);
await new Promise((res) => setTimeout(res, 5000));
setBlocks((b) => [...b, new Block(Math.random().toString(), "2")]);
};
return (
<div>
{blocks.map((b) => (
<div key={b.id}>{b.text}</div>
))}
<button onClick={handleClick}>click</button>
</div>
);
}@Rex Can you send me the code you tested with ?
it works with simple example but I think you page is more complex than this so it might not work as expected
try with flushSync first and see if you have performance issue
RexOP
My page is actually similar to yours, this the the part where I have teh user input, and above it I have the a div that contains the messagaes blocks
<form
ref={formRef}
action={handleQuery}
className="flex gap-x-2 w-3/4 py-4 mt-auto"
>
<input
name="query"
type="text"
placeholder="Ask about your data"
className=" input input-bordered rounded-full w-full"
/>
<button
disabled={pending}
type="submit"
className="btn btn-circle text-gray-600 disabled:text-gray-300"
>
<IoSend size={20} />
</button>
</form>RexOP
Well, this is coming from Next.js server action https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#client-components
but your function doesn't look like a server action at all
try put it in
onSubmit propsRexOP
But it should work in a way similar to
onSubmit@Rex But it should work in a way similar to `onSubmit`
are you sure lol
it doesn't do anything to me
oh yea it does
it is the exact behaviour you have
just change action props to onSubmit props and it should work
Answer
RexOP
OK this actually solved it.
Thanks 🚀
no prob