React Patterns
Compose Internals - Subcomponents Read From Context, Not Props
March 3, 2026
When you build a compound component - a family of pieces like Composer.Frame, Composer.Input, Composer.Submit - the worst path is passing state and handlers through every level via props. The better path: compose internals. Subcomponents read from a shared context. The parent provides that context; the pieces just consume it. Same UI, swappable state.
The problem: prop drilling inside the compound component
function ComposerFrame({ children, value, onChange, onSubmit }) {
return <form onSubmit={onSubmit}>{children}</form>;
}
function ComposerInput({ value, onChange }) {
return <TextInput value={value} onChange={onChange} />;
}
// Usage: you have to wire every piece
<ComposerFrame value={input} onChange={setInput} onSubmit={handleSubmit}>
<ComposerInput value={input} onChange={setInput} />
<ComposerSubmit onSubmit={handleSubmit} />
</ComposerFrame>;Every slice of state and every callback has to be passed down. The compound component is just a prop relay. Adding a new field or action means touching every layer.
The fix: one context, subcomponents that use it
Define a single context (e.g. state, actions, meta). The provider supplies it; subcomponents read it with use() (or useContext in React 18). No props for shared state.
const ComposerContext = createContext<ComposerContextValue | null>(null);
function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
return (
<ComposerContext.Provider value={{ state, actions, meta }}>
{children}
</ComposerContext.Provider>
);
}
function ComposerInput() {
const { state, actions, meta } = use(ComposerContext);
return (
<TextInput
ref={meta.inputRef}
value={state.input}
onChangeText={(text) => actions.update((s) => ({ ...s, input: text }))}
/>
);
}
function ComposerSubmit() {
const { actions } = use(ComposerContext);
return <Button onPress={actions.submit}>Send</Button>;
}
// Usage: provider wires state once; pieces compose
<Composer.Provider state={state} actions={actions} meta={meta}>
<Composer.Frame>
<Composer.Input />
<Composer.Footer>
<Composer.Formatting />
<Composer.Submit />
</Composer.Footer>
</Composer.Frame>
</Composer.Provider>;The UI pieces don't know whether state comes from useState, Zustand, or a server hook. They only depend on the context interface. That's composing internals: subcomponents read from context, not from props, so you can swap implementations without changing the component tree.
Related content
Build a Redux Store From Scratch - Learn State Management by Implementing It
Implement a minimal Redux-style store from scratch: getState, dispatch, subscribe, and reducers. Understand how global state works and when to use it in real apps like todos or carts
Read moreComposition Over Configuration - Let Consumers Build, Not Configure
Stop piling boolean and config props onto one component. Let consumers compose the pieces they need - fewer props, clearer intent, and code that scales.
Read moreExplicit Component Variants - Name the Use Case, Drop the Booleans
One component with isThread, isEditing, isForwarding is hard to reason about. Create named variants - ThreadComposer, EditComposer - so each screen is explicit and self-documenting.
Read more