Skip to content

Conversation

@jacobwisniewski
Copy link

@jacobwisniewski jacobwisniewski commented Jan 4, 2026

Background

When using useChat with resume: true, refreshing the page mid-stream causes duplicate stream requests because React StrictMode runs effects twice (mount → unmount → mount). The current implementation has no protection against this:

useEffect(() => {
  if (resume) {
    chatRef.current.resumeStream();
  }
}, [resume, chatRef]);

This leads to:

  • Duplicated/repeated text in the UI
  • Multiple concurrent stream consumers
  • Inconsistent message state
  • onData callbacks being called twice per server write

Summary

This PR adds a ref guard to prevent resumeStream() from being called twice when React StrictMode double-invokes effects during development.

Add a hasResumedRef that tracks whether resume has already been attempted:

const hasResumedRef = useRef(false);

useEffect(() => {
  if (resume && !hasResumedRef.current) {
    hasResumedRef.current = true;
    chatRef.current.resumeStream();
  }
}, [resume, chatRef]);

Testing

Added a test case that renders the component in <React.StrictMode> and verifies only one resume request is made despite effects running twice.

Related Issues

Fixes #9610

@vercel-ai-sdk vercel-ai-sdk bot added the ai/ui label Jan 4, 2026
@jacobwisniewski jacobwisniewski force-pushed the fix/use-chat-strict-mode-resume branch 2 times, most recently from 763f0a4 to 7e31534 Compare January 5, 2026 00:23
@lgrammel
Copy link
Collaborator

lgrammel commented Jan 7, 2026

please add a patch changeset

@lgrammel
Copy link
Collaborator

lgrammel commented Jan 7, 2026

for future PRs please try to follow our PR description template

jacobwisniewski and others added 2 commits January 8, 2026 09:33
Add a ref guard to prevent resumeStream() from being called twice
when React StrictMode double-invokes effects during development.

This fixes an issue where refreshing the page mid-stream would cause
duplicate stream requests, leading to:
- Duplicated/repeated text in the UI
- Multiple concurrent stream consumers
- Inconsistent message state

The fix adds a hasResumedRef that tracks whether resume has already
been attempted, preventing the second invocation from making another
request.

Fixes vercel#9610
@jacobwisniewski jacobwisniewski force-pushed the fix/use-chat-strict-mode-resume branch from 7420ac1 to 806d9d4 Compare January 7, 2026 22:33
@jacobwisniewski
Copy link
Author

please add a patch changeset

Done!

for future PRs please try to follow our PR description template

Will do!

Comment on lines +113 to 120
const hasResumedRef = useRef(false);

useEffect(() => {
if (resume) {
if (resume && !hasResumedRef.current) {
hasResumedRef.current = true;
chatRef.current.resumeStream();
}
}, [resume, chatRef]);
Copy link
Collaborator

@lgrammel lgrammel Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the ref need to be reset to false? maybe i missing something here, want to make sure this works on the several resumes not just the 1st one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

onData callback called twice per writer.write() when using resumable streams

2 participants