# Streaming Best Practices URL: /concepts/streaming/streaming-best-practices This page provides copy-paste ready component patterns for common streaming scenarios. Each pattern shows what belongs in the schema vs state, and why. ## Pattern 1: Read-Only Display For components that display AI-generated content without user interaction. **Schema:** All display props **State:** None needed ```tsx import { z } from "zod"; import { type TamboComponent, useTamboStreamStatus } from "@tambo-ai/react"; const SummaryCardPropsSchema = z.object({ title: z.string().describe("Brief title for the summary."), highlights: z.array(z.string()).describe("Key points to display."), conclusion: z.string().describe("Final takeaway."), }); type SummaryCardProps = z.infer; export const SummaryCard: TamboComponent = { name: "SummaryCard", description: "Displays a summary with key highlights.", propsSchema: SummaryCardPropsSchema, component: function SummaryCardComponent({ title, highlights, conclusion, }: SummaryCardProps) { const { streamStatus, propStatus } = useTamboStreamStatus(); return (
{propStatus.title?.isStreaming && (
)} {propStatus.title?.isSuccess &&

{title}

}
    {highlights?.map((item, i) => (
  • {item}
  • ))}
{propStatus.conclusion?.isStreaming && (
)} {propStatus.conclusion?.isSuccess &&

{conclusion}

} {streamStatus.isError && (

Failed to load summary.

)}
); }, }; ``` *** ## Pattern 2: AI-Generated → User-Editable For components where AI generates initial content that users can edit. **Schema:** `subject`, `body` (AI generates these) **State (visible to AI):** Same keys - user edits override AI values **Why:** AI sees user's edits for follow-up messages like "make it more formal" ```tsx import * as React from "react"; import { z } from "zod"; import { type TamboComponent, useTamboComponentState, useTamboStreamStatus, } from "@tambo-ai/react"; const EmailComposerPropsSchema = z.object({ subject: z.string().describe("Short, clear subject line."), body: z.string().describe("Draft email body in plain text."), }); type EmailComposerProps = z.infer; type EmailDraft = { to: string; subject: string; body: string; }; export const EmailComposer: TamboComponent = { name: "EmailComposer", description: "Compose and edit an email before sending.", propsSchema: EmailComposerPropsSchema, component: function EmailComposerComponent({ subject, body, }: EmailComposerProps) { const { streamStatus, propStatus } = useTamboStreamStatus(); // Seed state from props as they stream in // Once user edits, their changes take precedence const [draft, setDraft] = useTamboComponentState( "emailDraft", { to: "", subject: "", body: "" }, { to: "", subject: subject ?? "", body: body ?? "" }, ); const isStreaming = streamStatus.isStreaming; function handleChange(field: keyof EmailDraft) { return (e: React.ChangeEvent) => { const value = e.target.value; setDraft((prev) => ({ ...prev, [field]: value })); }; } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); try { // Send draft to your backend await sendEmail(draft); } catch (err) { console.error("Failed to send:", err); } } return (