React Integration
@flue/react is the UI binding layer for Flue applications. It does not replace the runtime, the SDK client, or application routing; instead, it turns Flue's durable agent and workflow event streams into React state that components can render. Use it when a browser interface needs to follow a continuing agent conversation or a finite workflow run without manually stitching together snapshot loading, reconnects, and stream events. The React package is intentionally paired with @flue/sdk, which owns HTTP requests, authentication, custom headers, custom fetch behavior, and stream transport.
Sources: apps/docs/src/content/docs/guide/react.md
Purpose and Scope
The React integration solves the client-side state problem that appears once Flue resources are exposed over HTTP. Agents and workflows are durable server-side resources: the server stores accepted work and emits events as state changes. A React component, however, needs a coherent in-memory view that is safe to render, update optimistically, and reconcile after reconnects. The guide positions @flue/react as the package that bridges those two worlds by materializing durable event streams into live component state.
The package exposes two primary hooks for two different Flue resource shapes. useFlueAgent() is for an agent instance, which is identified by an agent name and an instance ID and can continue across multiple prompts. useFlueWorkflow() is for a finite workflow run, where the UI observes a bounded operation with its own run state and event history. This distinction matches Flue's broader model: agents are useful for continuing context, while workflows are useful for inspectable operations that complete.
A key integration boundary is that React state is downstream of the SDK client. The application creates one client with createFlueClient({ baseUrl: '/api' }), passes that client to FlueProvider, and lets hooks consume it from context. Authentication, headers, CORS concerns, and custom fetch behavior belong on the SDK client and route layer, not inside each component. That keeps UI code focused on rendering messages, statuses, and workflow updates instead of duplicating transport policy.
Sources: apps/docs/src/content/docs/guide/react.md
Relevant Source Files
apps/docs/src/content/docs/guide/react.md- First-party React guide that documents installation,FlueProvider,useFlueAgent(),useFlueWorkflow(), SDK-client ownership of transport, route exposure requirements, optimistic message behavior, message parts, and stream materialization semantics.
Set Up React
Install the React and SDK packages together, then create a single SDK client near the root of the React application. The provider pattern matters because every hook needs the same transport configuration: base URL, authentication, headers, and any custom fetch implementation. In the docs example, the client is configured with a relative /api base URL, which fits applications that mount Flue routes behind the same origin as the frontend.
pnpm add @flue/react @flue/sdkimport { FlueProvider } from '@flue/react';
import { createFlueClient } from '@flue/sdk';
import { createRoot } from 'react-dom/client';
import { App } from './App.tsx';
const client = createFlueClient({ baseUrl: '/api' });
createRoot(document.getElementById('root')!).render(
<FlueProvider client={client}>
<App />
</FlueProvider>,
);Routing is a required part of the setup, not an optional deployment detail. The guide explicitly notes that the agent and workflow modules used by React must export route; mounting flue() alone does not expose those individual resources. In practice, this means the backend must make the relevant agent or workflow HTTP endpoints available and protect them with the same authorization policy the SDK client is configured to satisfy. Cross-origin applications should solve CORS and credentials at the routing layer before debugging hook behavior.
Sources: apps/docs/src/content/docs/guide/react.md
Agent Conversations
An agent conversation is addressed by a pair: the agent name and an instance ID. The name selects the discovered server-side agent, and the ID selects the durable conversation instance to observe. useFlueAgent({ name, id }) reconstructs the transcript from durable events, publishes it into React state, and then follows new events. Components should treat messages as the canonical ordered transcript supplied by Flue rather than sorting or merging events themselves.
import { useFlueAgent } from '@flue/react';
import { useState } from 'react';
export function Chat({ conversationId }: { conversationId: string }) {
const [input, setInput] = useState('');
const agent = useFlueAgent({
name: 'support-assistant',
id: conversationId,
});
async function submit(event: React.FormEvent) {
event.preventDefault();
const message = input.trim();
if (!message) return;
setInput('');
await agent.sendMessage(message);
}
return (
<section>
<div aria-live="polite">
{agent.messages.map((message) => (
<article key={message.id}>
<strong>{message.role}</strong>
{message.parts.map((part) =>
part.type === 'text' ? <p key={part.text}>{part.text}</p> : null,
)}
</article>
))}
</div>
<form onSubmit={submit}>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button disabled={!input.trim()} type="submit">
Send
</button>
</form>
</section>
);
}sendMessage() is designed for responsive chat interfaces. It adds the user message immediately and resolves when the server admits the prompt, not when model generation is finished. That means a form can clear its input and continue rendering while generation proceeds through the stream. When the durable copy of that message arrives, the hook reconciles it with the optimistic entry without changing the transcript position. Use the hook's status value to distinguish connection, submission, streaming, and error states in the UI.
Sources: apps/docs/src/content/docs/guide/react.md
Message Shape and Rendering
Messages returned by the hook are Flue-owned FlueConversationMessage values with a parts-based structure. The documented part types are text, reasoning, dynamic-tool, and file. A simple chat interface may only render text parts, but production interfaces usually need to account for tool calls, reasoning displays, and attachments. The important contract is that applications should branch on the Flue part type instead of assuming the transcript is a flat string array.
Validated structured tool output is preserved on the dynamic-tool part's output, which lets a React application render specialized tool UI without subscribing to a separate data-event channel. File attachments are represented as durable file parts that carry media type only; historical bytes are not served through this projection. If an upload preview must show the original bytes, render that preview optimistically from local browser data while the durable transcript keeps the canonical attachment metadata.
The docs also draw a boundary around ecosystem compatibility: these message values are not AI SDK types, and @flue/react neither depends on ai at runtime nor implements the AI SDK transport protocol. That matters when integrating existing chat components. Adapters should translate from Flue's parts-based messages into the component's expected shape, rather than expecting the hook to emit another library's transport or message schema.
Sources: apps/docs/src/content/docs/guide/react.md
Reducer and Streaming Behavior
The hook behavior is best understood as a reducer over a durable stream, with a snapshot step before live following. The guide states that useFlueAgent() uses the SDK's materialized agents.observe() layer: it loads the complete canonical snapshot, publishes it atomically in durable order, and continues from that exact checkpoint through reconnects and canonical resets. This is why consumers do not need to coordinate snapshot events with live events or re-sort messages after each render.
historyReady is the signal that the requested durable history has loaded as one coherent snapshot. Once it becomes true, it remains true through later live reconnects, so components can use it to decide when to leave an initial loading state without flickering during network interruptions. Live observation follows updates with Durable Streams long-polling, which keeps the browser synchronized without requiring component code to understand polling checkpoints or reset recovery.
This reducer behavior is the reason optimistic submission can be safe. The component submits a prompt, displays the local user message immediately, and then receives durable stream events that either confirm the canonical history or reset it. Because the materialized layer owns ordering and reconciliation, the React layer can expose a stable transcript abstraction. UI code should still render error and submission states explicitly, but it should not implement its own durable history merge algorithm.
Sources: apps/docs/src/content/docs/guide/react.md
API Components
| Component | Role | Notes |
|---|---|---|
FlueProvider | Places a configured SDK client in React context. | Wrap the application once near the root. |
createFlueClient() | SDK factory used before rendering React. | Configure baseUrl, auth, headers, and custom fetch here. |
useFlueAgent({ name, id }) | Observes and submits to a continuing agent instance. | Returns messages, sendMessage(), status, and historyReady behavior described by the guide. |
sendMessage(message) | Submits a user prompt to the agent instance. | Resolves when the server admits the prompt; generation continues through the stream. |
useFlueWorkflow() | Observes a finite workflow run. | Use for workflow UIs rather than continuing conversations. |
FlueConversationMessage | Durable transcript message shape. | Contains parts such as text, reasoning, dynamic-tool, and file. |
For workflow interfaces, start from the same provider and SDK-client setup, then use useFlueWorkflow() to observe a finite run instead of an open-ended conversation. A workflow UI should usually emphasize run lifecycle, validated inputs and outputs, and read-only event history after completion. An agent UI should emphasize message composition, conversation identity, optimistic user prompts, and ongoing stream state. Both rely on the same backend requirement: the resource must be exposed over HTTP with the appropriate exported route.
Sources: apps/docs/src/content/docs/guide/react.md
Integration Checklist
Before building custom UI, confirm the backend and frontend contracts line up. Install both packages, create one SDK client, wrap the React tree in FlueProvider, and mount the Flue API at the baseUrl used by that client. Confirm that each agent or workflow module the UI calls exports route, because the docs warn that mounting the application alone does not expose those modules. Then build components around hook state instead of lower-level stream handling.
When rendering an agent transcript, use message IDs as keys, branch over part types, and prefer accessible live regions such as aria-live="polite" for newly streamed content. Disable empty submissions, clear local input after accepting a non-empty prompt, and show distinct UI for connecting, submitting, streaming, and error states. For richer applications, render dynamic-tool.output with domain-specific controls and handle file uploads with local optimistic previews when historical bytes are needed.
Next, read Routing to expose and protect the HTTP API, Building Agents to define route-enabled agent modules, Workflows to decide when a finite run is the better shape, and the SDK Overview for client transport details. React should be the rendering layer on top of those pieces: the runtime owns durable execution, the SDK owns transport, and @flue/react owns live state projection into components.
Sources: apps/docs/src/content/docs/guide/react.md