# How to Implement Real-Time AI-Managed State in React Components with Tambo

> Implement real-time AI-managed state in React with Tambo. Stream AG-UI events, let LLMs update components instantly, and simplify state synchronization.

- Repository: [tambo ai/tambo](https://github.com/tambo-ai/tambo)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Tambo's React SDK enables real-time AI-managed state by streaming AG-UI events through a reducer that accumulates component props, allowing LLM-generated updates to render instantly in registered React components without manual state synchronization.**

The `tambo-ai/tambo` repository provides a React SDK that bridges large language model outputs directly to your UI components. By implementing real-time AI-managed state in React components with Tambo, you can create dynamic interfaces where the AI progressively constructs or modifies visual elements through a streaming event architecture.

## Understanding the Event Streaming Architecture

### AG-UI Events and the Stream Context

The SDK implements an AG-UI (Agent-User Interface) protocol where `useTambo` establishes a stream context that receives low-level events from the Tambo AI server. These events include `tambo.component.start`, `tambo.component.props_delta`, and `tambo.component.end`, each representing a distinct phase in the component lifecycle.

### The streamReducer and Event Accumulation

Located in [`react-sdk/src/v1/utils/event-accumulator.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/utils/event-accumulator.ts), the `streamReducer` function translates incoming events into immutable thread state:

- `tambo.component.start` creates a component content block with `streamingState: "started"`
- `tambo.component.props_delta` applies a JSON Patch to the component's props and sets `streamingState: "streaming"`
- `tambo.component.end` finalizes the block with `streamingState: "done"`

The reducer guarantees immutability by returning fresh `ThreadState` objects for every event, preventing side effects and enabling predictable state transitions.

## Registering Components for AI Control

Before the LLM can manage component state, you must register React components with the SDK. The `registerComponent` function (utilized via [`react-sdk/src/v1/providers/tambo-registry-provider.tsx`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/providers/tambo-registry-provider.tsx)) maps component names to React components and optional Zod-style validation schemas.

```typescript
import { registerComponent } from '@tambo-ai/react';
import MyChart from '@/components/MyChart';
import { z } from 'zod';

registerComponent('my_chart', {
  component: MyChart,
  props: {
    '~standard': z.object({
      data: z.array(z.number()),
      title: z.string(),
    }),
  },
});

```

## Consuming the AI-Managed State

### The useTambo Hook

The `useTambo` hook (defined in [`react-sdk/src/v1/hooks/use-tambo-v1.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/hooks/use-tambo-v1.ts)) memoizes the thread state, messages, and component registry. It returns `isStreaming` to indicate active LLM communication and provides access to the accumulated thread state. The hook also handles `PLACEHOLDER_THREAD_ID` for optimistic UI updates before the API returns a real thread ID.

```typescript
import { useTambo } from '@tambo-ai/react';

export function Chat() {
  const {
    messages,
    isStreaming,
    registerComponent,
  } = useTambo('my_thread');

  return (
    <div className="space-y-4">
      {messages.map((msg) => (
        <Message key={msg.id} message={msg} />
      ))}
      {isStreaming && <LoadingSpinner />}
    </div>
  );
}

```

### Rendering Components with ComponentRenderer

The `ComponentRenderer` (implemented in [`react-sdk/src/v1/components/v1-component-renderer.tsx`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/components/v1-component-renderer.tsx)) reads component content blocks and handles partial JSON parsing using the *partial-json* library. It validates props against registered schemas and renders the actual React element.

```tsx
import { ComponentRenderer } from '@tambo-ai/react';

function Message({ message }) {
  return (
    <div className="p-2 bg-gray-50 rounded">
      {message.content.map((c) => {
        if (c.type === 'component') {
          return (
            <ComponentRenderer
              key={c.id}
              content={c}
              threadId={message.threadId}
              messageId={message.id}
              fallback={<div>Unknown component: {c.name}</div>}
            />
          );
        }
        return <p>{c.text}</p>;
      })}
    </div>
  );
}

```

### Accessing Props with useTamboComponentState

Inside registered components, the `useTamboComponentState` hook accesses the AI-managed props. Because the `ComponentContentProvider` supplies context values including `componentId`, `threadId`, `messageId`, and `componentName`, and because the rendered element is memoized on `content.id`, React preserves the component instance across updates. This enables **in-place** state updates as the LLM streams new props via `tambo.component.props_delta` events.

```tsx
import { useTamboComponentState } from '@tambo-ai/react';

export function MyChart() {
  const { props } = useTamboComponentState();
  // Props update in-place as TOOL_CALL_ARGS / COMPONENT_PROPS_DELTA events arrive
  return <Chart data={props.data} title={props.title} />;
}

```

## How the SDK Maintains Real-Time Synchronization

### Immutability and Thread State

The `streamReducer` in [`react-sdk/src/v1/utils/event-accumulator.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/utils/event-accumulator.ts) guarantees immutability by returning fresh `ThreadState` objects for every event. This prevents side effects and enables predictable state transitions. The system uses `UnreachableCaseError` for exhaustive type checking on unknown event types during development, ensuring all AG-UI events are handled correctly.

### In-Place Updates and Memoization

The [`v1-component-renderer.tsx`](https://github.com/tambo-ai/tambo/blob/main/v1-component-renderer.tsx) uses React's memoization on `content.id` to ensure that as `props_delta` events arrive, the same component instance receives updated props rather than unmounting and remounting. This is critical for maintaining internal React state (like form inputs or animation states) while the AI progressively updates the component's configuration. The wire-up between the client and reducer occurs in [`react-sdk/src/v1/utils/stream-handler.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/utils/stream-handler.ts), which manages the React `useReducer` and `useEffect` integration.

## Summary

- **Tambo's React SDK** bridges LLM outputs to React components through an AG-UI event streaming protocol implemented in [`react-sdk/src/v1/utils/stream-handler.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/utils/stream-handler.ts).
- **Event accumulation** in [`react-sdk/src/v1/utils/event-accumulator.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/utils/event-accumulator.ts) translates `tambo.component.*` events into immutable thread state with `streamingState` tracking ("started", "streaming", "done").
- **Component registration** via `registerComponent` maps AI-referenced names to React components with optional Zod validation, managed by [`tambo-registry-provider.tsx`](https://github.com/tambo-ai/tambo/blob/main/tambo-registry-provider.tsx).
- **Real-time rendering** uses `ComponentRenderer` to handle partial JSON parsing and schema validation, while `useTambo` and `useTamboComponentState` provide access to AI-managed state.
- **In-place updates** are achieved through memoization on `content.id` in [`v1-component-renderer.tsx`](https://github.com/tambo-ai/tambo/blob/main/v1-component-renderer.tsx), allowing React to preserve component instances as the LLM streams `props_delta` events.

## Frequently Asked Questions

### What types of components can I register with Tambo?

You can register any React component that accepts props. The SDK supports Zod-style validation schemas for type safety, and components can range from simple UI elements like buttons and cards to complex interactive widgets like charts, forms, and maps. The only requirement is that the component must be registered with a unique name that the LLM can reference when emitting `tambo.component.start` events.

### How does Tambo handle partial or incomplete JSON during streaming?

The `ComponentRenderer` in [`react-sdk/src/v1/components/v1-component-renderer.tsx`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/components/v1-component-renderer.tsx) uses the *partial-json* library to parse incoming props during the streaming phase. This allows the component to render with partial data as soon as the first `tambo.component.props_delta` event arrives, rather than waiting for the complete JSON payload. As subsequent delta events arrive, the props are patched in-place, creating a progressive rendering effect.

### Can I use Tambo with Next.js App Router?

Yes, Tambo's React SDK is compatible with Next.js App Router. The hooks (`useTambo`, `useTamboComponentState`) and providers work within client components. You should wrap your application or specific routes with the Tambo provider in a client component boundary (using the `"use client"` directive), then use the hooks in child components to access the AI-managed state. The SDK handles thread management and streaming entirely on the client side via [`react-sdk/src/v1/utils/stream-handler.ts`](https://github.com/tambo-ai/tambo/blob/main/react-sdk/src/v1/utils/stream-handler.ts).

### What happens if the LLM emits an unknown component name?

If the LLM emits a `tambo.component.start` event with a component name that hasn't been registered via `registerComponent`, the `ComponentRenderer` will render the `fallback` prop provided to it. This allows you to display a graceful error message or placeholder UI when the AI attempts to use a component that doesn't exist in your registry. The SDK also performs exhaustive type checking with `UnreachableCaseError` for unknown event types during development to ensure all AG-UI events are handled correctly.