Stackblitz: https://stackblitz.com/~/github.com/techybolek/DUP-TESTCASE
git: https://github.com/techybolek/DUP-TESTCASE
This example is intended to be a testcase demonstrating a duplicate hook for the following scenario:
- The user types in a query and presses the Send button
- A new Conversation object is initialized and added to the list existing conversations
- An invokeServerAPI function is called with the query as the parameter and the callback function to be invoked for each response steps. There may be multiple response steps for each query and I want to display them immediately as they come in from the server
- The response is added to the current conversation. However the setConversations hook is invoked twice for each step, even though updateCurrentConversation is called only once.
Console output:
page.tsx:25 Text response received: Response for query:sample query 1
page.tsx:31 Invoke updateCurrentConversation with response: Response for query:sample query 1
page.tsx:31 Invoke updateCurrentConversation with response: Response for query:sample query 1
Source:
"use client"
import React, { useState, useRef } from 'react';
export default function MyApp() {
const [input, setInput] = useState('');
const [conversations, setConversations] = useState([]);
const messagesEndRef = useRef(null);
const handleSubmit = async (event) => {
event.preventDefault();
const startTime = new Date()
const newConversation = { query: input, startTime, responses: [] }
setConversations((prevConversations) => [...prevConversations, newConversation]);
try {
await invokeServerAPI(input, textHandler)
}
finally {
setInput('');
}
function textHandler(text) {
console.log("Text response received:", text)
updateCurrentConversation(text)
}
function updateCurrentConversation(data) {
setConversations((prevConversations) => {
console.log('Invoke updateCurrentConversation with response: ', data)
const index = prevConversations.findIndex(conversation => conversation.startTime === startTime);
const _newConversation = { ...prevConversations[index] };
_newConversation.responses.push({ data })
return [
...prevConversations.slice(0, index),
_newConversation,
...prevConversations.slice(index + 1)
];
});
}
};
return (
<div className="flex flex-col h-screen">
<div className="overflow-y-scroll flex-grow p-2">
{conversations.map((conversation, index) => (
<div key={index} className="mb-5">
<p>{conversation.startTime.toLocaleString()} <span className="font-bold">{conversation.query})</span>:</p>
{conversation.responses
.map((response, respIndex) => {
return (
<div key={index * 1000 + respIndex} className="mb-2">
<pre className="mt-2 bg-gray-100 p-2 rounded border border-gray-300 whitespace-pre-wrap">{response.data}</pre>
</div>
);
})
}
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="p-2 bg-gray-800 text-white">
<form onSubmit={handleSubmit}>
<div className="flex justify-start space-x-4">
<div className="flex flex-col" style={{ marginRight: '1rem', marginLeft: '0' }}>
<label className="text-black" style={{ color: 'black', display: 'block', width: 'auto' }}>Query:</label>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
className="p-2 bg-gray-300 text-black rounded-md"
style={{ fontSize: '18px', color: 'black', height: '40px', width: '500px' }}
placeholder="Enter your query here"
/>
</div>
<div className="flex flex-col items-start">
<label style={{ color: 'black', display: 'block', width: 'auto' }}> </label>
<button type="submit" className="bg-green-500 text-white rounded-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-opacity-50"
style={{ paddingLeft: '1rem', paddingRight: '1rem', height: '40px', marginLeft: '0' }}>Send</button>
</div>
</div>
</form>
</div>
</div >
);
}
export function invokeServerAPI(theQuery, theHandler) {
return new Promise((resolve, reject) => {
//simulate just one response
theHandler('Response for query:' + theQuery)
resolve(null);
})
}
3
Answers
If there is a
React.StrictMode
in the index.js file remove that.The setState invokes twice in strict mode in React. In Next JS to turn off strict mode edit your next.config.mjs as shown below and your strict mode will be turned off.
You can also checkout this link
Hope this helps!
Try to define the state update functionality in an isolated function that returns the desired new state and set it when updating. Log the behavior in the same way – will it change?