TL;DR
OuseEnhanceChildrené um hook customizado que permite injetar ou mesclar props do componente pai em seus filhos — ideal para compound components sem precisar usar Context API.
✅ Sem boilerplate, com tipagem forte e suporte a hierarquias aninhadas.
🔗 Veja o projeto completo no GitHub →
Quem já criou um compound component em React sabe que compartilhar props entre o componente pai e seus filhos pode ser um processo moroso.
A abordagem mais comum é usar a Context API, mas, sinceramente... sinto que estou usando uma bazuca pra matar uma formiga. Tudo o que eu queria era que os subcomponentes do Root tivessem acesso às mesmas props — sem precisar criar contextos, providers e hooks para cada caso.
😫 O problema
Existem basicamente duas formas tradicionais de compartilhar props:
- Repetir as props manualmente nos subcomponentes, o que é pouco elegante.
- Usar Context API, o que adiciona mais complexidade e código do que o necessário em muitos casos.
Se o Root recebe as props e envolve os subcomponentes, faz sentido que eles também tenham acesso a essas props de forma prática.
🤔 “Mas por que não passar as props direto nos filhos?”
Boa pergunta.
Em alguns casos, faz todo sentido — por exemplo, quando o Root não usa essas props.
Mas pense em situações em que o Root também precisa delas.
Um caso clássico é a prop id.
Todos os elementos React aceitam id, e ela costuma ser útil em testes.
Seria ótimo poder passar um único id para o Root e fazer com que cada subcomponente tivesse um ID derivado automaticamente, como:
<Root id="user">
<Root.Header /> // id="user-header"
<Root.Body /> // id="user-body"
<Root.Footer /> // id="user-footer"
</Root>
Esse mesmo raciocínio vale para className, disabled, atributos data-*, entre outros.
Sim, o Context resolveria.
Mas ele adiciona boilerplate, complexidade e até listeners internos de pub/sub — algo desnecessário quando queremos apenas espelhar props.
🧠 A solução: useEnhanceChildren
Apresento o useEnhanceChildren, um custom hook que facilita a injeção de props do componente pai nos filhos.
Ele deve ser utilizado dentro do componente Root, e conta com dois modos de operação:
1. Modo broadcast
No modo broadcast, o hook injeta o mesmo conjunto de props em todos os filhos, de forma uniforme (exceto elementos HTML nativos como <div> e <span>).
const children = useEnhanceChildren(props.children, {
props: { disabled: true, className: 'root-child' }
});
2. Modo map
No modo map, você especifica quais props vão para quais filhos, utilizando o displayName de cada subcomponente.
const children = useEnhanceChildren(props.children, {
mapProps: {
Header: { color: 'blue' },
Footer: { color: 'gray' },
}
});
🔑 O displayName é essencial nesse modo.
É por meio dele que o hook identifica qual subcomponente deve receber quais props:
function Header(props: { color?: string }) { /* ... */ }
Header.displayName = 'Header';
🧩 Type-safety com generics
Você pode definir um generic para garantir que os nomes e as props estejam corretos, evitando erros de digitação e garantindo a inferência de tipos adequada.
type MapProps = {
'Card.Header': { title?: string };
'Card.Footer': { description?: string };
};
const enhanced = useEnhanceChildren<MapProps>(children, {
mapProps: {
'Card.Header': { title: 'Título' },
'Card.Footer': { description: 'Descrição' },
// Erro de tipagem se você tentar algo errado, por exemplo:
// 'Body': { wrongProp: true } ❌
},
});
🧱 Exemplo completo
Para visualizar o useEnhanceChildren em ação dentro de um compound component real, veja o exemplo abaixo onde o componente Card injeta suas props (title, description) diretamente nos filhos via useEnhanceChildren — sem Context, sem prop drilling manual.
import type { ReactNode, ComponentPropsWithoutRef } from 'react';
import { useEnhanceChildren } from '@/hooks/useEnhanceChildren';
type CardProps = {
title: string;
description: string;
children: ReactNode; // children será automaticamente omitido pelo hook
};
/*
type CardMapProps = {
'Card.Header': { title: string };
'Card.Footer': { description: string };
};
*/
export function Card({ children, ...props }: CardProps) {
const enhancedChildren = useEnhanceChildren<CardProps>(children, {
props, // modo broadcast
});
/*
const enhancedChildren = useEnhanceChildren(children, {
mapProps: { // modo map
'Card.Header': { title: props.title },
'Card.Footer': { description: props.description },
},
});
*/
return (
<div>
{enhancedChildren}
</div>
);
}
// ----------------------
// Card.Header
// ----------------------
type CardHeaderProps = ComponentPropsWithoutRef<'header'> & {
title?: string;
};
Card.Header = Object.assign(
({ title, ...rest }: CardHeaderProps) => (
<header {...rest}>
{title}
</header>
),
{ displayName: 'Card.Header' },
);
// ----------------------
// Card.Body
// ----------------------
type CardBodyProps = ComponentPropsWithoutRef<'main'>;
Card.Body = Object.assign(
({ children, ...rest }: CardBodyProps) => (
<main {...rest}>
{children}
</main>
),
{ displayName: 'Card.Body' },
);
// ----------------------
// Card.Footer
// ----------------------
type CardFooterProps = ComponentPropsWithoutRef<'footer'> & {
description?: string;
};
Card.Footer = Object.assign(
({ description, ...rest }: CardFooterProps) => (
<footer {...rest}>
{description}
</footer>
),
{ displayName: 'Card.Footer' },
);
E seu uso na prática ficaria assim:
<Card title="Título principal" description="Descrição resumida">
<Card.Header />
<Card.Body>
<p>Conteúdo interno do card.</p>
</Card.Body>
<Card.Footer />
</Card>
Recursos e comportamento
✅ Recursividade: percorre hierarquias aninhadas — netos, bisnetos e tataranetos também recebem as props.
✅ Precedência correta: props passadas diretamente nos filhos têm prioridade (não são sobrescritas).
✅ Ignora elementos HTML nativos.
✅ Suporte a TypeScript forte: overloads e união discriminada para os modos map e broadcast.
✅ Memoização interna: evita re-renderizações desnecessárias.
Conclusão
O useEnhanceChildren nasceu da necessidade de simplificar algo bem específico: compartilhar props do Root com seus filhos em compound components — sem recorrer à complexidade da Context API.
Mas, embora tenha sido pensado para esse cenário, acredito que ele pode ser útil em outros contextos onde há herança natural de props. E, claro, ainda há espaço para evoluir — tanto em recursos quanto em ergonomia.
O hook não substitui o Context, mas preenche com elegância o espaço entre o prop drilling manual e o context overkill.
Se você trabalha com compound components, recomendo experimentar — e, por que não, contribuir com melhorias.
💬 Dúvidas, críticas e sugestões também são muito bem-vindas.

Top comments (0)