S

Overview

Primitives for building streaming UI with React.

The @stream.ui/react package provides primitives for handling partial/streaming data in React applications.

Installation

npm install @stream.ui/react
pnpm add @stream.ui/react
yarn add @stream.ui/react
bun add @stream.ui/react

Usage

import { Stream } from "@stream.ui/react";

function MyComponent() {
  const { object, isLoading, error } = useObject({ schema: mySchema });

  return (
    <Stream.Root data={object} isLoading={isLoading} error={error}>
      <Stream.Field fallback={<Skeleton />}>
        <h1>{object?.title}</h1>
      </Stream.Field>
      
      <Stream.List fallback={<ItemsSkeleton />}>
        {object?.items?.map(item => <Item key={item.id} {...item} />)}
      </Stream.List>
      
      <Stream.When loading>
        <Spinner />
      </Stream.When>
      
      <Stream.When error>
        {(err) => <ErrorMessage error={err} />}
      </Stream.When>
    </Stream.Root>
  );
}

Primitives

PrimitiveDescription
Stream.RootProvides context with streaming data and state
Stream.FieldRenders content with fallback when undefined
Stream.ListRenders arrays that grow as data streams in
Stream.WhenConditionally renders based on stream state

How It Works

When you use useObject from AI SDK, data arrives incrementally:

t=0: undefined
t=1: { title: "Hello" }
t=2: { title: "Hello", items: [] }
t=3: { title: "Hello", items: [{ id: 1 }] }
t=4: { title: "Hello", items: [{ id: 1 }, { id: 2 }] }
...

The primitives handle this automatically:

  1. Stream.Root tracks the state (loading → streaming → complete)
  2. Stream.Field shows fallback until values are defined
  3. Stream.List renders items as they arrive
  4. Stream.When shows different UI based on state

States

StateCondition
idleNo data, not loading
loadingLoading, no data yet
streamingLoading, partial data exists
completeNot loading, data exists
errorAn error occurred

TypeScript

The primitives work with your typed objects from useObject. Since you access your data directly (e.g., object?.title), you get full type safety and autocompletion from your schema.

import { Stream } from "@stream.ui/react";
import { experimental_useObject as useObject } from "@ai-sdk/react";

// Your schema defines the type
const articleSchema = z.object({
  title: z.string(),
  summary: z.string(),
  sections: z.array(z.object({ heading: z.string(), content: z.string() })),
});

function ArticleCard() {
  const { object, isLoading, error } = useObject({
    api: "/api/article",
    schema: articleSchema,
  });

  return (
    <Stream.Root data={object} isLoading={isLoading} error={error}>
      {/* object?.title is typed as string | undefined */}
      <Stream.Field fallback={<Skeleton />}>
        <h1>{object?.title}</h1>
      </Stream.Field>
      
      {/* object?.summary is typed as string | undefined */}
      <Stream.Field fallback={<Skeleton />}>
        <p>{object?.summary}</p>
      </Stream.Field>

      {/* object?.sections is typed as the array from schema */}
      <Stream.List fallback={<Skeleton />}>
        {object?.sections?.map(section => <Section key={section.heading} {...section} />)}
      </Stream.List>
    </Stream.Root>
  );
}

Types are also exported for advanced use cases:

import type {
  DeepPartial,
  StreamState,
  StreamContextValue,
  StreamRootProps,
  StreamFieldProps,
  StreamListProps,
  StreamWhenProps,
} from "@stream.ui/react";

On this page