The user hits send while the reply is in flight. You need a rule for that message: wait, ignore, or cut in.
By default the message waits in queue. It sends after the current run succeeds.
useChat exposes queue separately from messages. cancelQueued(id) drops an item before it sends:
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
const { messages, queue, sendMessage, cancelQueued } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});function PendingQueue() {
return (
<>
{queue.map((item) => (
<div key={item.id} className="pending">
{typeof item.content === "string" ? item.content : "[attachment]"}
<button onClick={() => cancelQueued(item.id)}>Cancel</button>
</div>
))}
</>
);
}Render queue with a different style from messages. Once the text appears in queue or messages, clear the composer.
Pass a queue option. A string is shorthand for { whenBusy }:
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
const { sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
queue: { whenBusy: "queue", drain: "fifo", maxSize: 5 },
});whenBusy is what happens to a send that arrives while the client is busy. The client is busy when a stream is active, a send is in flight, or the queue is draining:
Override the policy for one send with the second argument to sendMessage:
sendMessage("Never mind, do this instead", {
whenBusy: "interrupt",
body: { source: "composer" },
});body is extra JSON for that request only. It is merged into forwardedProps.
The composer no longer fights the stream. A send while busy waits, drops, or cuts in, on purpose.