Tool Call Rendering
By default, useChat only processes text_delta events. To display tool calls, you need two things:
- A custom
onEventhandler to capturetool_callevents - A
tool_callcase in your rendering switch to display them
Handling Tool Call Events
Section titled “Handling Tool Call Events”Provide onEvent to handle both text_delta and tool_call events:
const { messages, isLoading, sendMessage, stop } = useChat({ api: "http://localhost:8000/chat/", body: { session_id: "3" }, onEvent: (event, helpers) => { switch (event.type) { case "text_delta": helpers.appendText(event.delta); break; case "tool_call": helpers.appendPart({ type: "tool_call", tool_name: event.tool_name, argument: event.argument, }); break; } },});Rendering Tool Calls
Section titled “Rendering Tool Calls”The tool_call part has two fields: tool_name (the function name) and argument (a JSON string with the call arguments). Render them by adding a tool_call case to your switch:
{messages.map((msg) => ( <div key={msg.id}> <strong>{msg.role}:</strong> {msg.parts.map((part, i) => { switch (part.type) { case "text": return <span key={i}>{part.text}</span>; case "tool_call": return ( <pre key={i} style={{ opacity: 0.7, fontSize: "0.85em" }}> [Tool: {part.tool_name}]{"\n"} {JSON.stringify(JSON.parse(part.argument), null, 2)} </pre> ); default: return null; } })} </div>))}The argument field is a JSON string. Use JSON.parse to format it as readable JSON.
Full Example
Section titled “Full Example”A complete component that handles and renders tool calls:
import { useChat } from "@devscalelabs/react-sse-chat";
function Chat() { const { messages, isLoading, sendMessage, stop } = useChat({ api: "http://localhost:8000/chat/", body: { session_id: "3" }, onEvent: (event, helpers) => { switch (event.type) { case "text_delta": helpers.appendText(event.delta); break; case "tool_call": helpers.appendPart({ type: "tool_call", tool_name: event.tool_name, argument: event.argument, }); break; } }, });
return ( <div> {messages.map((msg) => ( <div key={msg.id}> <strong>{msg.role}:</strong> {msg.parts.map((part, i) => { switch (part.type) { case "text": return <span key={i}>{part.text}</span>; case "tool_call": return ( <pre key={i} style={{ opacity: 0.7, fontSize: "0.85em" }}> [Tool: {part.tool_name}]{"\n"} {JSON.stringify(JSON.parse(part.argument), null, 2)} </pre> ); default: return null; } })} </div> ))}
{isLoading && ( <button type="button" onClick={stop}> Stop </button> )}
<form onSubmit={(e) => { e.preventDefault(); const input = e.currentTarget.elements.namedItem( "message", ) as HTMLInputElement; sendMessage(input.value); input.value = ""; }} > <input name="message" placeholder="Type a message..." /> <button type="submit" disabled={isLoading}> Send </button> </form> </div> );}Without Tool Calls
Section titled “Without Tool Calls”If your backend only emits text_delta events (no tool calling), you don’t need onEvent at all. The default handler takes care of it:
const { messages, sendMessage } = useChat({ api: "/chat",});See the Quick Example for a minimal text-only setup.
ToolCallPart Shape
Section titled “ToolCallPart Shape”The built-in ToolCallPart type:
interface ToolCallPart { type: "tool_call"; tool_name: string; argument: string; // JSON string}If you need additional fields (e.g. tool_call_id, status), or your backend sends custom event types beyond text_delta and tool_call, see Custom Event Handling for extending both part types and event types with generics.