| name | composition-patterns |
| description | | Use when this capability is needed. |
React Composition Patterns
Composition patterns for flexible and maintainable React components.
Avoid boolean prop abuse, use compound components, state lifting, and composing internals.
Rule Categories (by Priority)
| Priority | Category | Impact | Description |
|---|
| 1 | Component Architecture | HIGH | Component structuring |
| 2 | State Management | MEDIUM | State management patterns |
| 3 | Implementation Patterns | MEDIUM | Implementation patterns |
| 4 | React 19 APIs | MEDIUM | React 19 changes |
1. Component Architecture (HIGH)
1.1 Prevent Boolean Prop Abuse
Impact: CRITICAL
Boolean props lead to combinatorial explosion. Use composition instead.
function Composer({
isThread,
isDMThread,
isEditing,
isForwarding,
}: Props) {
return (
<form>
{isDMThread ? <DMField /> : isThread ? <ThreadField /> : null}
{isEditing ? <EditActions /> : isForwarding ? <ForwardActions /> : <DefaultActions />}
</form>
)
}
function ThreadComposer({ channelId }: { channelId: string }) {
return (
<Composer.Frame>
<Composer.Input />
<AlsoSendToChannelField id={channelId} />
<Composer.Footer>
<Composer.Submit />
</Composer.Footer>
</Composer.Frame>
)
}
function EditComposer() {
return (
<Composer.Frame>
<Composer.Input />
<Composer.Footer>
<Composer.CancelEdit />
<Composer.SaveEdit />
</Composer.Footer>
</Composer.Frame>
)
}
1.2 Use Compound Components
Impact: HIGH
Structure complex components into subcomponents connected by shared context.
function Composer({
renderHeader,
renderFooter,
showAttachments,
}: Props) {
return (
<form>
{renderHeader?.()}
<Input />
{showAttachments && <Attachments />}
{renderFooter?.()}
</form>
)
}
const ComposerContext = createContext<ComposerContextValue | null>(null)
function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
return (
<ComposerContext value={{ state, actions, meta }}>
{children}
</ComposerContext>
)
}
function ComposerInput() {
const { state, actions: { update } } = use(ComposerContext)
return <TextInput value={state.input} onChangeText={text => update(s => ({ ...s, input: text }))} />
}
= {
: ,
: ,
: ,
: ,
: ,
}
<. state={state} actions={actions} meta={meta}>
</.>
2. State Management (MEDIUM)
2.1 Separate State Implementation from UI
Impact: MEDIUM
Provider knows state implementation, UI only uses context interface.
function ChannelComposer({ channelId }: { channelId: string }) {
const state = useGlobalChannelState(channelId)
const { submit } = useChannelSync(channelId)
return <Composer.Input value={state.input} />
}
function ChannelProvider({ channelId, children }: Props) {
const { state, update, submit } = useGlobalChannel(channelId)
return (
<Composer.Provider state={state} actions={{ update, submit }}>
{children}
</Composer.Provider>
)
}
function ChannelComposer() {
return (
<Composer.Frame>
<Composer.Input /> {/* Reads state from context */}
<Composer.Submit />
</Composer.Frame>
)
}
2.2 Define Generic Context Interface
Impact: HIGH
Define generic interface with 3 parts: state, actions, meta.
interface ComposerContextValue {
state: {
input: string
attachments: Attachment[]
isSubmitting: boolean
}
actions: {
update: (updater: (state: State) => State) => void
submit: () => void
}
meta: {
inputRef: React.RefObject<TextInput>
}
}
function ForwardMessageProvider({ children }) {
const [state, setState] = useState(initialState)
return <ComposerContext value={{ state, actions: { update: setState, submit }, meta }}>{children}</ComposerContext>
}
function ChannelProvider({ channelId, children }) {
const { state, update, submit } = useGlobalChannel(channelId)
return
}
2.3 Lift State to Provider
Impact: HIGH
Lift state to Provider so sibling components can access it.
function ForwardMessageDialog() {
return (
<Dialog>
<ForwardMessageComposer /> {/* state trapped here */}
<MessagePreview /> {/* Cannot access state! */}
<ForwardButton /> {/* Cannot call submit! */}
</Dialog>
)
}
function ForwardMessageDialog() {
return (
<ForwardMessageProvider>
<Dialog>
<ForwardMessageComposer />
<MessagePreview /> {/* Access state via context */}
<ForwardButton /> {/* Call submit via context */}
</Dialog>
</ForwardMessageProvider>
)
}
function ForwardButton() {
const { actions } = use(ComposerContext)
return <Button onPress={actions.submit}>Forward</Button>
}
3. Implementation Patterns (MEDIUM)
3.1 Create Explicit Component Variants
Impact: MEDIUM
Create explicit variant components instead of boolean props.
<Composer isThread isEditing={false} channelId="abc" showAttachments />
<ThreadComposer channelId="abc" />
<EditMessageComposer messageId="xyz" />
<ForwardMessageComposer messageId="123" />
3.2 Prefer Children over Render Props
Impact: MEDIUM
Use children for composition instead of renderX props.
<Composer
renderHeader={() => <CustomHeader />}
renderFooter={() => <><Formatting /><Emojis /></>}
/>
<Composer.Frame>
<CustomHeader />
<Composer.Input />
<Composer.Footer>
<Composer.Formatting />
<Composer.Emojis />
</Composer.Footer>
</Composer.Frame>
When render props appropriate: Parent needs to pass data to children
<List data={items} renderItem={({ item, index }) => <Item item={item} />} />
4. React 19 APIs (MEDIUM)
⚠️ React 19+ only. Skip this section for React 18 or below.
4.1 React 19 API Changes
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
return <TextInput ref={ref} {...props} />
})
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
return <TextInput ref={ref} {...props} />
}
const value = useContext(MyContext)
const value = use(MyContext)
Quick Checklist
| Check | Rule |
|---|
| [ ] | 3+ Boolean props? → Refactor to composition |
| [ ] | Complex conditional rendering? → Create explicit variants |
| [ ] | State trapped in component? → Lift to Provider |
| [ ] | renderX props? → Change to children |
| [ ] | React 19? → Remove forwardRef, use use() |
Related Skills
/react-best-practices - Performance optimization (waterfall, bundle, rendering)
/web-design-guidelines - UI/UX quality (accessibility, interaction)
/design-patterns - General design patterns
Converted and distributed by TomeVault — claim your Tome and manage your conversions.