useView

useView subscribes to a session's default view and returns the visible messages, branch navigation, write operations, and pagination state. It is the primary hook for rendering a conversation UI.

By default the hook subscribes to the nearest ClientSessionProvider's default view. Pass session to use a different session's default view, or view to subscribe to a specific view created with useCreateView or session.createView().

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import { useView } from '@ably/ai-transport/react';

function Conversation() {
  const { messages, send } = useView({ limit: 30 });

  return (
    <>
      {messages.map(({ codecMessageId, message }) => (
        <Message key={codecMessageId} message={message} />
      ))}
      <Composer
        onSubmit={(text) =>
          send({ kind: 'user-message', message: { role: 'user', parts: [{ type: 'text', text }] } })
        }
      />
    </>
  );
}

This hook must be used within a ClientSessionProvider unless session or view is supplied explicitly.

Parameters

sessionoptionalClientSession<TInput, TOutput, TProjection, TMessage>
A client session whose default view to subscribe to. Defaults to the nearest provider.
viewoptionalClientView<TInput, TMessage>
A specific view to subscribe to directly. Takes priority over session.
limitoptionalNumber
Number of older codecMessages to reveal per page (exactly limit, fewer only at the end of history). When provided, auto-loads the first page on mount.
skipoptionalBoolean
When true, skip all subscriptions and return an empty handle immediately.

Returns

messagesCodecMessage<TMessage>[]
The visible messages along the selected branch, each paired with its codecMessageId. Read the domain object from each entry's message field.
hasOlderBoolean
Whether there are older messages that can be revealed via loadOlder.
loadingBoolean
Whether a page load is currently in progress.
loadErrorAbly.ErrorInfo or Undefined
Set when the most recent loadOlder call failed. Cleared automatically on the next successful load.
loadOlder() => Promise<CodecMessage<TMessage>[]>
Reveal older messages, resolving to the revealed page (oldest-first). Returns [] when nothing was revealed: already loading, no view resolved, history exhausted, or the load failed.
runOfRunInfo
Look up the RunInfo for the Run that owns the given codecMessageId.
runRunInfo
Direct lookup by Run id.
runs() => RunInfo[]
Snapshot of the visible Runs along the selected branch, in chronological order. Returns [] when the view isn't resolved.
branchSelectionBranchHandle
Resolve the BranchHandle anchored at codecMessageId: the sibling state plus a select(index) verb to navigate it. Always returns a safe handle.
send(events, options?) => Promise<ClientRun<TMessage>>
Send an input on the channel and fire a POST. At most one new message per send; the array form carries only wire-only inputs (tool results, approvals). Wrap a domain message via codec.createUserMessage (or build the { kind: 'user-message', message } literal) to send a fresh user message. Takes optional
.
regenerate(messageId, options?) => Promise<ClientRun<TMessage>>
Regenerate an assistant message using this view's branch for history.
edit(messageId, inputs, options?) => Promise<ClientRun<TMessage>>
Edit a user message, forking from this view's branch.

Reveal older messages

loadOlder(): Promise<CodecMessage<TMessage>[]>

Reveal older messages into the view, resolving to the revealed page (oldest-first). When limit is set on the hook, the first page auto-loads on mount. Call loadOlder again to reveal more. The call is gated so concurrent invocations collapse to one in-flight request; it returns [] when nothing was revealed (already loading, no view resolved, history exhausted, or the load failed).

On failure, loadError is set. On the next successful load, loadError is cleared automatically.

Look up a Run by message id

runOf(codecMessageId: string): RunInfo | undefined

Look up the RunInfo for the Run that owns the given codecMessageId. Returns undefined when the codec-message-id has not been observed.

Look up a Run by id

run(runId: string): RunInfo | undefined

Direct lookup by Run id. Symmetric with runOf for callers that already hold a runId.

Resolve a branch selection

branchSelection(codecMessageId: string): BranchHandle<TMessage>

Resolve the BranchHandle anchored at codecMessageId: the sibling state plus a select(index) verb to navigate it. Always returns a safe handle: for branch anchors with N siblings, siblings carries every sibling Run's view of the anchor slot; for non-anchor messages, siblings is [thisMessage] and hasSiblings is false.

Select a sibling

Switch the visible sibling through the BranchHandle the branchSelection call returns, rather than a separate hook method:

JavaScript

1

view.branchSelection(codecMessageId).select(1);

index is clamped to [0, siblings.length - 1]. The call is a silent no-op when the message is not a branch anchor, and emits 'update' on the underlying view when the visible output changes.

Send inputs

send(events: TInput | TInput[], options?: SendOptions): Promise<ClientRun<TMessage>>

Send an input on the channel and fire a POST. Each TInput carries its own routing metadata (parent, target, codecMessageId).

To send a fresh user message, build a UserMessage input. Use the codec factory (codec.createUserMessage(message)) or build the literal directly: { kind: 'user-message', message }. A send introduces at most one new message: exactly one UserMessage for a fresh send (which mints a new Run), or none for a continuation. The array form exists only to carry the wire-only inputs that resolve a single assistant turn (tool-result, tool-result-error, tool-approval-response); pair it with options.runId to extend a suspended Run. Passing more than one new message rejects with InvalidArgument.

Regenerate an assistant message

regenerate(messageId: string, options?: SendOptions): Promise<ClientRun<TMessage>>

Regenerate an assistant message. Creates a new Run that targets the message and threads under its parent user message. Both ids are computed automatically from this view's branch.

Edit a user message

edit(messageId: string, inputs: TInput | TInput[], options?: SendOptions): Promise<ClientRun<TMessage>>

Edit a user message. Creates a new Run that forks the target message with the replacement inputs. forkOf, parent, and history are computed automatically from this view's branch.

Example

A conversation component that loads older messages on mount, renders branch navigation when a sibling is available, and offers a regenerate button on each assistant turn.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

import { useView } from '@ably/ai-transport/react';

function Conversation() {
  const {
    messages,
    hasOlder,
    loadOlder,
    branchSelection,
    send,
    regenerate,
  } = useView({ limit: 30 });

  return (
    <>
      {hasOlder && <button onClick={() => loadOlder()}>Load older</button>}
      {messages.map(({ codecMessageId, message }) => {
        const branch = branchSelection(codecMessageId);
        return (
          <div key={codecMessageId}>
            <Message message={message} />
            {branch.hasSiblings && (
              <BranchControls
                selection={branch}
                onSelect={(i) => branch.select(i)}
              />
            )}
            {message.role === 'assistant' && (
              <button onClick={() => regenerate(codecMessageId)}>Regenerate</button>
            )}
          </div>
        );
      })}
      <Composer
        onSubmit={(text) =>
          send({ kind: 'user-message', message: { role: 'user', parts: [{ type: 'text', text }] } })
        }
      />
    </>
  );
}