20 React Compound Component
I’ve seen react component with dot <SomePrimitive.Something>
. Please explain what it is.
import { ThreadPrimitive } from "@assistant-ui/react";
<ThreadPrimitive.Root>
<ThreadPrimitive.Viewport>
<ThreadPrimitive.Empty />
<ThreadPrimitive.Messages
={{
components,
EditComposer,
UserMessage,
AssistantMessage
}}/>
<ThreadPrimitive.ScrollToBottom />
</ThreadPrimitive.Viewport>
<ThreadPrimitive.Suggestion />
<ThreadPrimitive.If />
</ThreadPrimitive.Root>
This is called the compound component pattern (or namespace components). Let me break it down for you.
20.1 What It Actually Is
The dot notation <ThreadPrimitive.Root>
is just JavaScript object property access in JSX. It’s not special React syntax—it’s regular JS!
// This is just a JavaScript object:
const ThreadPrimitive = {
Root: SomeComponent,
Viewport: AnotherComponent,
Messages: YetAnotherComponent,
// ... etc
}
// So when you write:
<ThreadPrimitive.Root />
// You're really just accessing:
.Root // which is a component ThreadPrimitive
20.2 Why Use This Pattern?
It groups related components together under a single namespace. Think of it like organizing widgets in Flutter packages, but at the component level:
Flutter (similar concept):
MaterialApp
├─ Scaffold.body
└─ Scaffold.appBar
React Compound Components:
ThreadPrimitive
├─ ThreadPrimitive.Root
├─ ThreadPrimitive.Viewport
└─ ThreadPrimitive.Messages
20.3 How It’s Created
Here’s a simple example:
// ============================================
// Option 1: Direct object creation
// ============================================
const Card = {
: ({ children }) => <div className="card">{children}</div>,
Root: ({ children }) => <div className="card-header">{children}</div>,
Header: ({ children }) => <div className="card-body">{children}</div>,
Body;
}
// Usage:
<Card.Root>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
</Card.Root>
// ============================================
// Option 2: Attaching to main component (more common)
// ============================================
function Card({ children }) {
return <div className="card">{children}</div>;
}
.Header = ({ children }) => <div className="card-header">{children}</div>;
Card.Body = ({ children }) => <div className="card-body">{children}</div>;
Card
// Usage is the same:
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
</Card>
20.4 Benefits
- Clear relationship - You instantly know
ThreadPrimitive.Root
is part of theThreadPrimitive
family - Better autocomplete - Type
ThreadPrimitive.
and your IDE shows all available components - Cleaner imports - Import once, get all related components:
// Instead of:
import { ThreadRoot, ThreadViewport, ThreadMessages } from "...";
// You get:
import { ThreadPrimitive } from "...";
// Then use: ThreadPrimitive.Root, ThreadPrimitive.Viewport, etc.
- Prevents naming collisions - Many libraries might have a “Root” component, but
ThreadPrimitive.Root
is unique
20.5 Connection to Your Flutter Experience
If you’ve used Flutter, this might feel familiar to how some widgets organize their constructors:
// Flutter
.medium(...)
AppBar.borderless(...)
TextField
// React compound components
<Card.Root>
<Card.Header>