Developing compound components in React often presents a common dilemma: how to efficiently share props between a parent component and its nested children without resorting to verbose or overly complex solutions. While React’s Context API is powerful, it can feel like overkill for the simple task of distributing props. This article introduces useEnhanceChildren, a custom hook designed to streamline this process, enabling seamless prop injection and merging from parent to child components, complete with strong typing and support for nested hierarchies.
The Prop Sharing Conundrum in React
When building compound components, developers frequently encounter two main approaches for prop distribution:
1. Manual Prop Repetition: This involves explicitly passing the same props down to each child component, which is cumbersome, error-prone, and clutters the code.
2. Context API Overkill: While Context API is excellent for global state management, using it solely for simple prop sharing among closely related components introduces unnecessary boilerplate (creating contexts, providers, and custom hooks) and complexity.
Consider a scenario where a Root component needs to pass an id prop, intending for its subcomponents to automatically derive their own unique IDs (e.g., id="user-header", id="user-body"). Manually passing these IDs is tedious, and setting up Context for just this purpose feels disproportionate. The natural expectation is for the parent component to easily “distribute” its relevant props to its children.
Enter useEnhanceChildren: A Smarter Approach to Prop Injection
useEnhanceChildren is a custom React hook that simplifies the injection or merging of props from a parent component into its children. It’s designed to be used within the parent (Root) component and offers two flexible modes of operation:
1. Broadcast Mode
In broadcast mode, the hook uniformly injects a specified set of props into all eligible child components. This is ideal when you want to apply the same properties across multiple children (e.g., disabled state, a common className). Native HTML elements (like <div> or <span>) are intelligently ignored, ensuring only your custom components receive the injected props.
const children = useEnhanceChildren(props.children, {
props: { disabled: true, className: 'root-child' }
});
This snippet would pass disabled: true and className: 'root-child' to all direct children of the component where useEnhanceChildren is called.
2. Map Mode
For more granular control, map mode allows you to specify which props go to specific child components based on their displayName. This provides precise targeting, enabling different children to receive different sets of props.
const children = useEnhanceChildren(props.children, {
mapProps: {
Header: { color: 'blue' },
Footer: { color: 'gray' },
}
});
For map mode to function correctly, each subcomponent must have a displayName property defined. For instance:
function Header(props: { color?: string }) { /* ... */ }
Header.displayName = 'Header';
Type-Safety with Generics
useEnhanceChildren prioritizes developer experience and robustness through strong TypeScript support. By defining a generic type for mapProps, you can ensure correct prop names and types, leveraging type inference and preventing common errors at compile time.
type CardMapProps = {
'Card.Header': { title?: string };
'Card.Footer': { description?: string };
};
const enhanced = useEnhanceChildren<CardMapProps>(children, {
mapProps: {
'Card.Header': { title: 'Título' },
'Card.Footer': { description: 'Descrição' },
// 'Body': { wrongProp: true } // This would trigger a type error
},
});
Practical Application: The Card Component Example
Imagine building a Card compound component with Card.Header, Card.Body, and Card.Footer subcomponents. Instead of passing title to Card.Header and description to Card.Footer explicitly from the Card parent, useEnhanceChildren can automate this.
The Card component would use useEnhanceChildren in broadcast mode (or map mode for more specific needs) to inject its title and description props into its children.
<Card title="Main Title" description="Brief Description">
<Card.Header /> {/* Receives title="Main Title" automatically */}
<Card.Body>
<p>Internal card content.</p>
</Card.Body>
<Card.Footer /> {/* Receives description="Brief Description" automatically */}
</Card>
This simplifies the consumer’s code and centralizes prop management within the parent.
Key Features and Advantages:
- Recursivity: The hook intelligently traverses nested component hierarchies, ensuring props reach grandchildren and beyond.
- Correct Precedence: Props explicitly passed to child components directly will always take precedence over props injected by
useEnhanceChildren, preventing unintended overwrites. - Ignores Native HTML: Only custom React components receive prop enhancements.
- Robust TypeScript Support: Provides strong typing with overloads and discriminated unions for both broadcast and map modes.
- Internal Memoization: Optimizes performance by preventing unnecessary re-renders of children.
Conclusion
useEnhanceChildren provides an elegant and efficient solution for a specific, yet common, challenge in React development: sharing props within compound components. It expertly fills the gap between the tediousness of manual prop drilling and the potential over-engineering of the Context API for simpler scenarios. While it doesn’t replace Context for global state or highly dynamic prop changes, it offers a clean, type-safe, and performant alternative for many prop inheritance use cases. Developers working with compound components are encouraged to explore this hook and consider contributing to its ongoing evolution.
Explore the Project
For a deeper dive, full code examples, and to contribute, visit the useEnhanceChildren project on GitHub: https://github.com/lucaspanizio/use-enhance-children