Composition patterns for building flexible, maintainable React components. Avoid
boolean prop proliferation by using compound components, lifting state, and
composing internals. These patterns make codebases easier to work with as they
scale.
When to Apply
Refactoring components with many boolean props
Building reusable component libraries
Designing flexible component APIs
Working with compound components or context providers
Pattern Overview
#
Pattern
Impact
1
Avoid Boolean Props
CRITICAL
2
Compound Components
HIGH
3
Context Interface (DI)
HIGH
4
State Lifting
HIGH
5
Explicit Variants
MEDIUM
6
Children Over Render Props
MEDIUM
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install react-composition
1. Avoid Boolean Prop Proliferation
Don't add boolean props like isThread, isEditing, isDMThread to customize
behavior. Each boolean doubles possible states and creates unmaintainable
conditional logic. Use composition instead.
Define a generic interface with state, actions, and meta. Any provider
implements this contract — enabling the same UI to work with different state
implementations. The provider is the only place that knows how state is managed.
// Provider A: Local state for ephemeral forms
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState(initialState)
return (
{children}
)
}
// Provider B: Global synced state for channels
function ChannelProvider({ channelId, children }: Props) {
const { state, update, submit } = useGlobalChannel(channelId)
return (
{children}
)
}
Swap the provider, keep the UI. Same Composer.Input works with both.
4. Lift State into Providers
Move state into dedicated provider components so sibling components outside the
main UI can access and modify state without prop drilling or refs.
// BAD — state trapped inside component; siblings can't access it
function ForwardMessageComposer() {
const [state, setState] = useState(initialState)
return
}
function ForwardMessageDialog() {
return (
)
}
// GOOD — state lifted to provider; any descendant can access it
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState(initialState)
const submit = useForwardMessage()
return (
{children}
)
}
function ForwardMessageDialog() {
return (
)
}
function ForwardButton() {
const { actions } = use(Composer.Context)
return
}
Key insight: Components that need shared state don't have to be visually
nested — they just need to be within the same provider.
5. Explicit Variant Components
Instead of one component with many boolean props, create explicit variants.
Each composes the pieces it needs — self-documenting, no impossible states.
// BAD — what does this render?
// GOOD — immediately clear
Each variant is explicit about its provider/state, UI elements, and actions.
6. Children Over Render Props
Use children for composition instead of renderX props. Children are more
readable and compose naturally.
// BAD — render props
}
renderFooter={() => <>>}
/>
// GOOD — children composition
When render props are appropriate: When the parent needs to pass data back
(e.g., renderItem={({ item, index }) => ...}).