Ref: https://stackblitz.com/edit/react-ts-8ykcuf?file=index.tsx
I created a small example to replicate the issue I am facing.
I am trying to create a delayed effect with setTimeout inside useEffect. I can see from console.log that setTimeout has already triggered and I expect the DOM to be updated, but actually the DOM is not rendered until the next human interaction.
The side effect in the sample example is to simulate a bot appending new message after user has entered a new message.
import React, { useEffect, useState } from 'react';
import { render } from 'react-dom';
interface Chat {
messages: string[];
poster: string;
}
const App = () => {
const [activeChat, setActiveChat] = useState<Chat>({
poster: 'Adam',
messages: ['one', 'two', 'three'],
});
const [message, setMessage] = useState('');
const [isBotChat, setIsBotChat] = useState(false);
useEffect(() => {
if (isBotChat) {
setIsBotChat(false);
setTimeout(() => {
activeChat.messages.push('dsadsadsada');
console.log('setTimeout completed');
}, 500);
}
}, [isBotChat]);
const handleSubmit = (e) => {
e.preventDefault();
if (message !== '') {
activeChat.messages.push(message);
setMessage('');
setIsBotChat(true);
}
};
return (
<div>
<h1>Active Chat</h1>
<div>
{activeChat?.messages.map((m, index) => (
<div key={index}>{m}</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.currentTarget.value)}
/>
<button type="submit">Send Message</button>
</form>
</div>
);
};
render(<App />, document.getElementById('root'));