What is it?
Chatbot state is the data that makes the UI coherent across turns: the ordered list of messages, whether a reply is in flight, any partial streamed text, and errors. React holds that state in component state or a small store. The model itself is stateless; your UI and server re-send history as needed (Agent Memory: Short-Term, Long-Term and Beyond, What is a Context Window?).
Why it matters
Bugs in state show up as duplicated messages, lost drafts, or spinners that never clear. Clear state also clarifies security: tokens and API keys never belong in useState on the client.
How to do it
Recommended fields:
messages: { id, role: 'user'|'assistant'|'system', content }[]pending: booleanerror: string | nulldraft: stringfor the composer- optional
streamingId/ partial content while streaming
Patterns:
- Append user message optimistically, then append assistant on success.
- On failure, keep the user message, set
error, clearpending. - When talking to your API, send only the history slice you intend (trim for context limits on the server).
- Do not store
MODEL_API_KEYin React state orlocalStorage. - For streaming, update the last assistant message's content as tokens arrive (Streaming AI Responses for Chat UIs).
Example
const initial = { messages: [], pending: false, error: null, draft: "" };
function reducer(state, action) {
switch (action.type) {
case "draft": return { ...state, draft: action.value };
case "send":
return {
...state,
draft: "",
pending: true,
error: null,
messages: [...state.messages, { id: action.id, role: "user", content: action.text }],
};
case "receive":
return {
...state,
pending: false,
messages: [...state.messages, { id: action.id, role: "assistant", content: action.text }],
};
case "fail":
return { ...state, pending: false, error: action.error };
default: return state;
}
}
Wire the reducer to the UI in Building AI Interfaces with React.
When to use it
Use an explicit messages array as soon as you have more than one turn. Lift state if multiple components (sidebar history, main thread) need the same conversation.
Practical notes
As conversations grow, consider pagination or virtualised lists so thousands of DOM nodes do not appear at once. Store a client-generated 'id' on each message so streaming updates can target the correct row. If you support editing a past user message and regenerating, treat that as a branch: either truncate following messages or keep an alternate thread id. Separate "display history" from "model history" when the server strips tool traces the user should not see. Persist conversations to your backend (Databases for AI Applications) keyed by user, and reload them into the same state shape on refresh. Never stash OAuth tokens or vendor keys beside the messages array in 'localStorage'.
Common mistakes
- Mutating the messages array in place so React skips re-renders.
- Putting the API key in context "for convenience".
- Sending the entire unbounded history forever without server-side trimming.
- Losing the draft when an error occurs.