Skip to content

React Native AI message streaming not working - messages appear all at once #174

@christiannwamba

Description

@christiannwamba

Problem Description

AI message streaming using @convex-dev/agent does not work on React Native Expo app. Messages appear all at once after AI completes generation instead of streaming incrementally character-by-character.

Environment

  • Platform: React Native 0.76.9 (Expo ~52.0.46)
  • Convex Version: ^1.25.2
  • Convex Agent Version: @convex-dev/agent ^0.2.10
  • Metro Config: Added unstable_enablePackageExports: true for package.json exports resolution
  • Import Path: Using @convex-dev/agent/react (not /dist/react)
  • Backend: Correctly configured with saveStreamDeltas: true and syncStreams()
  • Other Clients: Web client using identical Convex backend works perfectly with streaming

Expected Behavior

  1. Message status should transition: "pending""streaming""success"
  2. useThreadMessages should receive streaming deltas from useStreamingThreadMessages
  3. Text should appear incrementally as AI generates response
  4. useSmoothText should animate text appearance character-by-character

Actual Behavior

  1. Message status jumps directly: "pending""success" (never shows "streaming")
  2. All messages have streaming: false field
  3. Complete text appears instantly when AI finishes generating (no incremental updates)
  4. messages object contains only ["results", "status", "isLoading", "loadMore"] keys - no streaming data

Diagnostic Logging Results

// What we observe:
[STREAM-CHECK] {
  resultsCount: 2,
  status: "success",
  streamEnabled: true,
  messageKeys: ["results", "status", "isLoading", "loadMore"],
  hasStreamsProperty: false,
  streamsValue: undefined,
  last3Messages: [
    { order: 1, streaming: false, status: "success" },
    { order: 2, streaming: false, status: "success" }
  ]
}

// Status progression observed:
[MSG-STATUS] { role: "assistant", status: "pending", textLen: 0 }
[MSG-STATUS] { role: "assistant", status: "success", textLen: 266 } // Full text arrives instantly

Key observation: streaming: false on ALL messages, and no streams property in the returned object from useThreadMessages.

Backend Configuration (Verified Working on Web)

// Backend saves deltas:
export const generateResponse = internalActionWithUser({
  handler: async (ctx, args) => {
    const result = await thread.streamText(
      { system: systemPrompt, messages: [toolMessage], tools },
      { 
        saveStreamDeltas: true,  // ✓ Enabled
        contextOptions: { recentMessages: 20 }
      }
    );
    await result.consumeStream();
  },
});

// Backend returns streams:
export const listThreadMessages = authorizedQuery({
  args: {
    threadId: z.string(),
    paginationOpts: convexToZod(paginationOptsValidator),
    streamArgs: convexToZod(vStreamArgs),  // ✓ Accepts stream args
  },
  handler: async (ctx, { threadId, paginationOpts, streamArgs }) => {
    const paginated = await chatAgent.listMessages(ctx, { threadId, paginationOpts });
    const streams = await chatAgent.syncStreams(ctx, { threadId, streamArgs });  // ✓ Syncs streams
    return { ...paginated, streams };  // ✓ Returns streams
  },
});

Client Usage

const messages = useThreadMessages(
  api.app.chat.listThreadMessages,
  selectedThreadId ? { threadId: selectedThreadId } : "skip",
  {
    initialNumItems: 50,
    stream: true,  // ✓ Streaming enabled
  }
);

const uiMessages = toUIMessages(messages.results ?? []);
// components/AI/components/MessageBubble.tsx
const [visibleText, smooth] = useSmoothText(text, {
  startStreaming: status === "streaming",  // Never true on React Native
});

Comparison: Web vs React Native

Web Client (Working):

  • Uses identical backend queries
  • Same useThreadMessages hook pattern
  • Streaming works correctly
  • Status shows "streaming" during generation
  • Text appears incrementally

React Native Client (Broken):

  • Uses identical backend queries
  • Same useThreadMessages hook pattern
  • Streaming does NOT work
  • Status jumps from "pending" to "success"
  • Text appears all at once when complete

Question

Is there a known React Native-specific limitation or configuration required for useThreadMessages streaming that differs from web? The fact that the web client works perfectly with the same backend suggests a client-side React Native issue.

Specifically:

  • Does useStreamingThreadMessages work correctly on React Native?
  • Are there any React Native-specific subscription/polling behaviors that might cause delayed delta arrival?
  • Could there be a timing issue where deltas arrive after the message completes?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions