Build complex components from composable parts
Structure a component with optional or rearrangeable parts as a set of compound components that callers compose as children, sharing state through context. Use render props only when the component must pass data back to the caller's content.
Implementation
- Export the parts together, such as
Composer.Frame,Composer.Input, andComposer.Submit, and let callers arrange them as children. - Share state and actions among the parts through a context provided by the root or a provider component, instead of threading props through every part.
- Replace
showXflags andrenderXprops for static structure with children. - Keep a render prop, such as
renderItem, when the component supplies data to each piece of caller content.
Rationale
A monolithic component needs a new prop for every optional part and every arrangement callers want. Callers then configure structure indirectly through flags and callbacks. With composable parts, callers write the structure they want directly, and adding a part does not change the existing API.
Examples
Application: Optional parts
Incorrect (counterexample):
<Composer
showAttachments
showFormatting={false}
renderHeader={() => <CustomHeader />}
renderActions={() => <SubmitButton />}
/>
Correct:
<Composer.Provider state={state} actions={actions}>
<Composer.Frame>
<CustomHeader />
<Composer.Input />
<Composer.Footer>
<Composer.Attachments />
<Composer.Submit />
</Composer.Footer>
</Composer.Frame>
</Composer.Provider>
Callers see and control exactly which parts appear and in what order.
Application: Content that needs data from the component
Correct:
<List items={items} renderItem={(item) => <ItemRow item={item} />} />
The list supplies each item, so a render prop is the right tool.
Validation
Check components with several showX or renderX props for static structure; they should expose composable parts instead.
A render prop that receives data from the component is not a violation.