A reducer is a pure function (state, action) => newState — the same shape Redux and useReducer use to make state changes predictable. Here you build the reducer behind a streaming chat: the assistant's reply does not arrive all at once, so the store has to open an empty message, append text deltas as they stream in, and finally mark it complete — all without ever mutating the previous state.
State is an array of messages, each { id, role, content, status }. Implement streamingChatStoreReducer(state, action) handling four actions:
ADD_USER_MESSAGE { id, content } — append a user message with status: 'done'.START_ASSISTANT_MESSAGE { id } — append an assistant message with empty content and status: 'streaming'.APPEND_DELTA { id, delta } — append delta to that message's content.FINALIZE_MESSAGE { id } — set that message's status to 'done'.type Message = { id: string; role: 'user' | 'assistant'; content: string; status: 'streaming' | 'done' };
function streamingChatStoreReducer(state: Message[] | undefined, action: { type: string; id?: string; content?: string; delta?: string }): Message[];
let s = streamingChatStoreReducer([], { type: 'START_ASSISTANT_MESSAGE', id: 'a1' });
// → [{ id: 'a1', role: 'assistant', content: '', status: 'streaming' }]
s = streamingChatStoreReducer(s, { type: 'APPEND_DELTA', id: 'a1', delta: 'Hi' });
// → [{ id: 'a1', role: 'assistant', content: 'Hi', status: 'streaming' }]
// A no-op action returns the exact same reference.
const state = [{ id: 'a1', role: 'assistant', content: 'x', status: 'done' }];
streamingChatStoreReducer(state, { type: 'UNKNOWN' }) === state; // true
state unchanged.state is undefined, treat it as [].Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.