I am trying to learn React and build a simple, but specialized, chatbot using OpenAI API along the way. I am currently able to send and receive messages, but I encounter an issue when displaying the messages. When I send a user message, the user message will display on the page, but only until the response arrives, at which the user message disappears.
My Chat component is shown below. My guess was that setMessages([...messages, botMessage]);
simply appended a message to the state of the current list of messages, though maybe I have misunderstood something there.
import React, { useState } from "react";
import { PaperAirplaneIcon } from "@heroicons/react/20/solid";
import axios from "axios";
import UserMessage from "./UserMessage";
import BotMessage from "./BotMessage";
const Chat = () => {
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState("");
const [loading, setLoading] = useState(false);
const [rules, setRules] = useState([]);
const handleInputSubmit = async (e) => {
e.preventDefault();
setLoading(true);
const userMessage = { type: "user", text: inputValue };
setMessages([...messages, userMessage]);
setInputValue("");
const botMessage = await getBotMessage(inputValue);
setMessages([...messages, botMessage]);
setLoading(false);
};
const getBotMessage = async (userMessage) => {
try {
const response = await axios.post("http://<url>/api/chat", {
message: userMessage,
});
console.log("response", response.data);
const botMessage = { type: "bot", text: response.data.response };
return botMessage;
} catch (e) {
console.log(e);
const botMessage = {
type: "bot",
text: "Something went wrong. Try again.",
};
return botMessage;
}
};
return (
<div className="flex flex-col items-center w-full h-full justify-between">
<h1 className="text-3xl font-bold my-5">Chatbot</h1>
<div className="w-full">
{messages.map((message, index) => {
if (message.type === "user") {
return <UserMessage key={index} text={message.text} />;
}
return <BotMessage key={index} text={message.text} />;
})}
<form
onSubmit={handleInputSubmit}
className="flex flex-row w-full mb-5 mt-2"
>
<input
className="border-2 rounded-md p-2 w-full"
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button type="submit">
<PaperAirplaneIcon className="w-6 h-6 text-green-500" />
</button>
</form>
</div>
</div>
);
};
export default Chat;
2
Answers
EDIT: The solution below works, but is not the optimal solution. Check the other answer.
As new to React, I don't understand why this is the way it is, but I found the answer.
For some reason the
setMessages([...messages, botMessage]);
can't see the updatedmessages
state array given bysetMessages([...messages, userMessage]);
three lines prior (when a user message is sent).The solution is to add both messages, when the bot message arrives like, so the
handleInputSubmit
becomes:If anyone can explain or provide a link to documentation explaining this, I would very much appreciate it.
You have the explanation in official documentation here:
Your call to
setMessages
doesn’t result in yourmessage
variable being immediately updated. If you think about it, it makes total sense asmessages
is defined as aconst
, so it can never be reassigned! On the next re-render, you’ll have a fresh newmessages
with the updated value.So your second call to
setMessages
is addingbotMessage
to the original array, effectively replacinguserMessage
.Something you could try is using updater functions like:
Updater functions are also executed on the next re-render, but they are applied in queue and always receive the most recent value of your state variable, so both elements will be properly added.