React API
@flow-state-dev/react — React hooks, renderers, and context providers.
Peer dependency: react ^18.0.0 || ^19.0.0
FlowProvider
import { FlowProvider } from "@flow-state-dev/react";
<FlowProvider
flowKind="my-app"
sessionId="optional-initial-session"
userId="devuser"
baseUrl="/api/flows"
renderers={{
message: MessageComponent,
reasoning: ReasoningComponent,
component: {
"chart": ChartComponent,
},
}}
>
{children}
</FlowProvider>
Nested providers merge renderers (child keys override parent keys).
Hooks
useFlow(options?)
Session lifecycle management.
const flow = useFlow({ autoCreateSession: true });
flow.sessions; // SessionDetail[]
flow.activeSessionId; // string | null
flow.createSession(); // Promise<string>
flow.selectSession(id); // void
useSession(sessionId, options?)
Primary hook for session data and actions.
const session = useSession(sessionId, {
items: true, // default
items: false, // skip items
items: { visibility: "ui" }, // filter by visibility
items: { includeTransient: false }, // exclude transient items
});
session.detail; // SessionDetail | null
session.snapshot; // SessionStateSnapshotResponse | null
session.items; // OutputItem[]
session.messages; // MessageItem[]
session.blockOutputs; // BlockOutputItem[]
session.functionCalls; // FunctionCallItem[]
session.isLoading; // boolean
session.isStreaming; // boolean
session.error; // Error | null
await session.sendAction("chat", { message: "Hello!" });
session.refresh();
useClientData(session, options)
Read client data values from session state snapshot.
// String array mode — subscribe by name
const data = useClientData(session, {
session: ["activePlan", "messageCount"],
user: ["preferences"],
});
// Schema mode — subscribe with type inference
const data = useClientData(session, {
session: {
activePlan: activePlanSchema,
},
});
useAction(options)
Low-level action execution.
const { execute, loading, error } = useAction({
flowKind: "my-app",
action: "chat",
userId: "devuser",
});
await execute({ message: "Hello!" });
useRequestStream(options)
Direct request-stream access.
const { items, status, isStreaming } = useRequestStream({
requestId,
filter: { itemTypes: ["message", "component"] },
});
Renderers
ItemRenderer
Render a single item using the registered renderer.
import { ItemRenderer } from "@flow-state-dev/react";
<ItemRenderer item={item} />
ItemsRenderer
Render a list of items.
import { ItemsRenderer } from "@flow-state-dev/react";
<ItemsRenderer items={session.items} />
Custom Renderers
import type { MessageItem } from "@flow-state-dev/core/items";
function ChatMessage({ item }: { item: MessageItem }) {
return <p>{item.role}: {item.content[0]?.text}</p>;
}
// Register in FlowProvider
<FlowProvider renderers={{ message: ChatMessage }}>
// Suppress a type
<FlowProvider renderers={{ status: false }}>
RendererRegistry
Type for the renderers map:
type RendererRegistry = {
message?: ComponentType<{ item: MessageItem }> | false;
reasoning?: ComponentType<{ item: ReasoningItem }> | false;
component?: Record<string, ComponentType<{ item: ComponentItem }>>;
container?: Record<string, ComponentType<{ item: ContainerItem }>>;
// ... other item types
};