So I encountered a problem with Next.js / React and mutating an array that is saved in state. I can't really wrap my head around it and figure out why it happens and how to fix it.
I tried Google search and stackoverflow but to be honest this didn't help much and I am unsure what the exact problem is and what to do about it.
So here is what I was trying to achieve when the problem occurred.
I built a simple Chatbot / ChatGPT implementation with Next.js that allows me to input a question or request, and send it to my API / Backend which would then query ChatGPT itself and return the result.
For details of the implementation see the code below.
Here is a breakdown of how it should work:
- The user enters the question into a simple HTML input
- The user clicks the "Send" button
- This triggers a method that first adds the input message to the array of all messages (input by user and output by ChatGPT), which should be displayed in a chronological order
- Then the method does a fetch to the API / Backend which in turn calls the OpenAI API with the input
- When there is a result (async) it gets added to the array of messages as well and should be displayed below the input message
What actually happens is that first I can see the input message, but after the fetch has completed it gets overwritten by the result message and I don't see both.
I am not completely sure why this happens. I know that React doesn't change the state immediately, but it happens asnyc. This could be one reason.
But I suspect the main reason is the closure that is formed to update the state. It uses the spread syntax to add the new message to the already existing array of messages. And in both cases when I update the array with the input and the result message, the closure does not have the updated state (array of messages), but it referes to an empty array. This is probably why the mutation of the array twice is not working and are overwriting each other, correct?
So is there an actual way to fix this?
Chatbot.tsx:
import {useState} from "react";
export default function Chatbot() {
const [inputMessage, setInputMessage] = useState('');
const [messages, setMessages] = useState([]);
/**
* Push a new message to array of messages
* @param {string} text
*/
const addMessage = (text) => {
setMessages([
...messages,
text,
])
}
const queryChatbot = () => {
// Add input message / question as new message
addMessage(inputMessage);
fetch('/api/chatbot', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
inputMessage,
}),
})
.then(response => response.json())
.then(data => {
if (data.response) {
// For successful response: Add response as new message
addMessage(data.response.output.response);
} else {
// Otherwise add error as new message
addMessage('Sorry, no response possible.');
}
})
.catch(error => {
// In error case we add a new message with an error
addMessage('Sorry, an error ocurred.');
});
}
return <>
<input type="text" placeholder="Send a message" onChange={(event) => setInputMessage(event.target.value)} />
<button onClick={queryChatbot}>Send message</button>
{messages.map(message => (
<p>{ message }</p>
))}
</>
}