# Streaming Messages

> **Info:** This is documentation for **Stream Chat React Native SDK v9**, which is no longer actively maintained. For up-to-date documentation, see the [latest version (v10)](https://getstream.io/chat/docs/sdk/react-native/).

`StreamingMessageView` renders a reply as the agent writes it: Markdown, code with syntax highlighting, tables and charts, typed out as the text grows, as ChatGPT does. [`AIConversation`](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/conversation-view/) shows every reply with it, and you can use it on its own in a view of yours:

```tsx
import { StreamingMessageView } from "stream-chat-react-native/ai-components";

<StreamingMessageView text={reply.text} />;
```

>
> **Info:** `StreamingMessageView` and `AITypingIndicatorView` are available from version 9.10.0 of `stream-chat-react-native` and `stream-chat-expo`, in the `stream-chat-react-native/ai-components` and `stream-chat-expo/ai-components` entry points. See the [Quickstart](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/#step-2-install-the-sdks) to install them. Render them inside `StreamTheme`, like the other AI components. Charts need `victory-native` and `@shopify/react-native-skia`.
>

## Type the reply out

Pass the reply's text as it grows. `StreamingMessageView` types out the text that's new since the last update and carries on from where it was. It keeps up with the agent: each new part is typed out within about 0.4 seconds, at least a letter a frame, so a fast model never leaves it behind. Pass `letterInterval` or `renderingLetterCount` to type at a fixed pace instead. A code block that the text has opened but not closed yet is closed for the view, so the text on screen is always valid Markdown.

The text there when the view mounts shows at once. That's why a stored reply doesn't type itself out again when the user scrolls back to it. Text that isn't a continuation of what's shown, such as a half-written reply the backend replaces with an error, or another message's text in a view a list reuses, shows at once too.

| Name                     | Type                           | Required | Description                                                                                                                                                                                                                                        |
| ------------------------ | ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`                   | `string`                       | yes      | The Markdown to render.                                                                                                                                                                                                                            |
| `letterInterval`         | `number`                       | no       | The time between steps of a fixed pace, in milliseconds. Without it and `renderingLetterCount`, the typing keeps up with the text as it arrives.                                                                                                   |
| `renderingLetterCount`   | `number`                       | no       | The letters written each step of a fixed pace. Defaults to `2` when `letterInterval` is given.                                                                                                                                                     |
| `onWritingChange`        | `(isWriting: boolean) => void` | no       | Called with `true` when the text starts typing itself out and `false` when it's done, such as to show what follows it once it is.                                                                                                                  |
| `onLink`                 | `(url: string) => void`        | no       | Called when the user presses a link. Without it, the link opens where the device opens it.                                                                                                                                                         |
| `rules`                  | `MarkdownRules`                | no       | Rules that are deeply merged over the default ones, based on the [`SimpleMarkdown` parsing engine](https://github.com/Khan/perseus/tree/main/packages/simple-markdown#adding-a-simple-extension). See [Change how it draws](#change-how-it-draws). |
| `paragraphNumberOfLines` | `number`                       | no       | The `numberOfLines` of the Markdown's paragraphs and text, such as to show a message in a cut-off form.                                                                                                                                            |

```tsx
<StreamingMessageView
  text={reply.text}
  letterInterval={5} // a step every 5 ms
  renderingLetterCount={3} // 3 letters a step
/>
```

To show the rest of the text at once, such as when the user taps a reply that's still typing, call `skipAnimation` on the view's ref:

```tsx
import { useRef } from "react";
import { Pressable } from "react-native";
import {
  StreamingMessageView,
  type StreamingMessageViewRef,
} from "stream-chat-react-native/ai-components";

export const Reply = ({ text }: { text: string }) => {
  const ref = useRef<StreamingMessageViewRef>(null);
  return (
    <Pressable onPress={() => ref.current?.skipAnimation()}>
      <StreamingMessageView ref={ref} text={text} />
    </Pressable>
  );
};
```

With Stream Chat, the reply's text arrives with every `message.updated` event while the backend streams it. [What your backend sends](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/conversation-state/#what-your-backend-sends) covers how to write it.

## In the message list

In the `MessageList` of the Chat SDK, a message that `Chat`'s `isMessageAIGenerated` marks renders with the SDK's own `StreamingMessageView`, which types its text out in the same way, as a regular message bubble. To render it with the one from the AI components instead, which draws Markdown, highlighted code and charts, override the slot through `WithComponents`. [SDK integration](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/sdk-integration/#streamingmessageview) shows how.

### Type text out your own way

To type text out in a view of your own, use the `useStreamingMessage` hook, which `StreamingMessageView` is built on. It takes the `text`, and optionally a fixed pace as `letterInterval` and `renderingLetterCount`, and returns the `streamedMessageText` typed out so far, `isWriting`, and `skipAnimation` to show the rest at once:

```tsx
import { Text } from "react-native";
import { useStreamingMessage } from "stream-chat-react-native/ai-components";

export const TypedReply = ({ text }: { text: string }) => {
  const { isWriting, streamedMessageText } = useStreamingMessage({
    letterInterval: 15,
    renderingLetterCount: 4,
    text,
  });

  return (
    <Text>
      {streamedMessageText}
      {isWriting ? "|" : ""}
    </Text>
  );
};
```

The typing follows the text this way:

1. The text there when the view mounts shows at once. Only text added later is typed out.
2. When `text` grows, typing goes on from where it was.
3. When `text` isn't a continuation of what's on screen, the new text shows at once.

To type out the message list's AI replies your own way, replace `StreamingMessageView` with a view built on the hook, as [`useStreamingMessage`](https://getstream.io/chat/docs/sdk/react-native/v9/ui-components/ai/hooks/#usestreamingmessage) shows. Keep the speed readable, and type out only the messages `isMessageAIGenerated` marks.

## Code and charts

`Markdown` is the renderer `StreamingMessageView` uses, with the standard Markdown: tables, inline code, headings, lists and links. Render it directly when the text is complete and doesn't need typing out:

```tsx
import { Markdown } from "stream-chat-react-native/ai-components";

<Markdown>{reply.text}</Markdown>;
```

Code blocks with a language render with syntax highlighting, and a button that copies the code when the app has a [clipboard library](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/#optional-libraries). On Android with the New Architecture, highlighted code is drawn by one native text view with color spans, which keeps long code blocks cheap to scroll.

A code block whose language is `vega-lite` (or `vegalite`, `chart`, `chartjs`, `echarts`, `highcharts` or `json`) and whose content is a [Vega-Lite](https://vega.github.io/vega-lite/) spec renders as a native chart instead, as does a `mermaid` pie chart. The marks are `bar`, `line`, `area`, `point` and `arc`:

````md
```vega-lite
{
  "data": {
    "values": [
      { "month": "Jan", "signups": 120 },
      { "month": "Feb", "signups": 180 },
      { "month": "Mar", "signups": 240 }
    ]
  },
  "mark": "bar",
  "encoding": {
    "x": { "field": "month", "type": "nominal" },
    "y": { "field": "signups", "type": "quantitative" }
  }
}
```
````

````md
```mermaid
pie title Signups by plan
  "Free" : 120
  "Team" : 60
```
````

A block that doesn't read as a chart renders as code instead of breaking the message. Charts are drawn with `victory-native` and Skia, so install both, as the Quickstart describes. To teach your agent to write them, describe the format in its instructions.

### Change how it draws

`rules` changes how the Markdown draws any element, or adds one of your own. They're deeply merged over the default rules, so pass only what you change. The rules are named after the elements: `heading`, `paragraph`, `link`, `image`, `inlineCode`, `codeBlock`, `table`, `list` and `blockQuote`, among others. To draw an element your own way, give its rule a `react` function, which gets the node, an `output` function that draws its content, and the parser's state:

```tsx
import { Text } from "react-native";
import {
  type MarkdownRules,
  type ReactOutput,
  StreamingMessageView,
} from "stream-chat-react-native/ai-components";

// Declared outside the component, so the view gets the same object on every render.
const rules: MarkdownRules = {
  heading: {
    react: (node, output: ReactOutput, state) => (
      <Text key={state.key} style={{ fontSize: 22, fontWeight: "700" }}>
        {output(node.content, state)}
      </Text>
    ),
  },
  // Leave images out.
  image: { match: () => null },
};

export const Reply = ({ text }: { text: string }) => (
  <StreamingMessageView rules={rules} text={text} />
);
```

A rule with a `match` that returns `null` never matches, which turns the element off. To change only the looks, such as the colors of a code block, use the theme's `markdown` key instead, as [Styling and texts](#styling-and-texts) shows.

## Show what the agent is doing

Before the first word of a reply arrives, the agent may spend a while thinking or calling tools. `AITypingIndicatorView` shows that it's working: a text of your choice, with a shimmer:

```tsx
import { AITypingIndicatorView } from "stream-chat-react-native/ai-components";

<AITypingIndicatorView text="Checking external sources" />;
```

With Stream Chat, the backend says what the agent is doing with `ai_indicator.*` events, and [`useAIState`](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/conversation-state/#follow-what-the-agent-is-doing) returns the channel's state. Show the indicator for the states you want, between `MessageList` and the composer, inside `Channel`:

```tsx
import { View } from "react-native";
import {
  AIStates,
  useAIState,
  useChannelContext,
} from "stream-chat-react-native";
import { AITypingIndicatorView } from "stream-chat-react-native/ai-components";

const indicatorText: Record<string, string> = {
  [AIStates.Thinking]: "Thinking",
  [AIStates.ExternalSources]: "Checking external sources",
};

export const AIIndicator = () => {
  const { channel } = useChannelContext();
  const { aiState } = useAIState(channel);
  const text = indicatorText[aiState];

  return text ? (
    <View style={{ paddingHorizontal: 24, paddingVertical: 12 }}>
      <AITypingIndicatorView text={text} />
    </View>
  ) : null;
};
```

Once the reply's text arrives, during `AI_STATE_GENERATING`, the text itself shows the agent is working, so the example hides the indicator. [Say what the agent is doing](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/conversation-state/#say-what-the-agent-is-doing) lists the states and the events that set them.

Both the Chat SDK and the AI components export an `AITypingIndicatorView` and a `StreamingMessageView`, with different props. The ones from `stream-chat-react-native` read the AI state and the message themselves, while the ones from `stream-chat-react-native/ai-components` take the `text` to show. Import `useAIState` and `AIStates` from the first, and the views you render yourself from the second.

In an `AIConversation`, show what the agent is doing in its `footer` with `AITypingIndicatorView`, and the agent thinking with `isThinking`, as [Conversation view](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/conversation-view/#show-the-agent-thinking) describes. A [controller](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/conversation-state/#a-conversation-controller) shows the thinking by itself.

## Styling and texts

The views take their colors from the AI components' theme, which `StreamTheme` provides. The theme's `markdown` key styles the Markdown, such as `codeBlockContainer`, `table` and `inlineCode`, and `aiTypingIndicatorView` styles the indicator's text. The `shimmer` color of the theme's `colors` is the highlight that sweeps across it:

```tsx
<StreamTheme
  style={{
    aiTypingIndicatorView: { text: { fontSize: 15 } },
    colors: { shimmer: "#ffffff" },
  }}
>
  {children}
</StreamTheme>
```

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/streaming-messages/](https://getstream.io/chat/docs/sdk/react-native/v9/ai-components/streaming-messages/).