From 8eced4df4e134590afca4a36e69142a7ea9c54f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:07:50 +0000 Subject: [PATCH] [v6.0] fix(react): preserve chat when id is undefined (#16797) Backport of #16484 to `release-v6.0`. Passing an explicitly-undefined `id` to `useChat` recreated the internal `Chat` instance on every render (because the `id` key exists in options while the generated chat ID never equals `undefined`), wiping messages and stream state; the fix changes the recreate guard to only apply when the provided `id` is non-nullish. The `use-chat.ts` guard change and the changeset applied cleanly; the regression test from the original commit conflicted only because `main`'s test file wraps everything in an outer `describe('use-chat')` block that does not exist on v6, so the new `describe('undefined id')` block was re-indented to match v6's flat test file structure with no semantic changes. Part of the v6.0 backport tracking issue #16767. --- .changeset/use-chat-nullish-id.md | 5 ++ packages/react/src/use-chat.ts | 4 +- packages/react/src/use-chat.ui.test.tsx | 76 +++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .changeset/use-chat-nullish-id.md diff --git a/.changeset/use-chat-nullish-id.md b/.changeset/use-chat-nullish-id.md new file mode 100644 index 000000000000..ca361e14f158 --- /dev/null +++ b/.changeset/use-chat-nullish-id.md @@ -0,0 +1,5 @@ +--- +"@ai-sdk/react": patch +--- + +Treat nullish `useChat` IDs the same as omitted IDs so the chat instance is not recreated on every render. diff --git a/packages/react/src/use-chat.ts b/packages/react/src/use-chat.ts index 819d77ba51cc..71df9a02830d 100644 --- a/packages/react/src/use-chat.ts +++ b/packages/react/src/use-chat.ts @@ -60,7 +60,9 @@ export function useChat({ const shouldRecreateChat = ('chat' in options && options.chat !== chatRef.current) || - ('id' in options && chatRef.current.id !== options.id); + ('id' in options && + options.id != null && + chatRef.current.id !== options.id); if (shouldRecreateChat) { chatRef.current = 'chat' in options ? options.chat : new Chat(options); diff --git a/packages/react/src/use-chat.ui.test.tsx b/packages/react/src/use-chat.ui.test.tsx index 84f0775923f7..a1619fd3ffc1 100644 --- a/packages/react/src/use-chat.ui.test.tsx +++ b/packages/react/src/use-chat.ui.test.tsx @@ -2216,6 +2216,82 @@ describe('id changes', () => { }); }); +describe('undefined id', () => { + setupTestComponent( + () => { + const [renderCount, setRenderCount] = React.useState(0); + const { + messages, + setMessages, + id: idKey, + } = useChat({ + id: undefined, + generateId: mockId(), + }); + + return ( +
+
{idKey}
+
{renderCount}
+
{JSON.stringify(messages, null, 2)}
+
+ ); + }, + { + init: TestComponent => , + }, + ); + + it('should not recreate chat when id is explicitly undefined', async () => { + const initialId = screen.getByTestId('id').textContent; + + await userEvent.click(screen.getByTestId('set-message')); + + await waitFor(() => { + expect( + JSON.parse(screen.getByTestId('messages').textContent ?? ''), + ).toStrictEqual([ + { + id: 'message-0', + role: 'user', + parts: [{ text: 'hi', type: 'text' }], + }, + ]); + }); + + await userEvent.click(screen.getByTestId('rerender')); + + expect(screen.getByTestId('id').textContent).toBe(initialId); + expect( + JSON.parse(screen.getByTestId('messages').textContent ?? ''), + ).toStrictEqual([ + { + id: 'message-0', + role: 'user', + parts: [{ text: 'hi', type: 'text' }], + }, + ]); + }); +}); + describe('chat instance changes', () => { setupTestComponent( () => {