Tipar Children no React com TypeScript
ReactNode, ReactElement, PropsWithChildren... qual usar? Descubra a diferenca e quando aplicar cada tipo de children no React com TypeScript.
Por que isso é importante
Tipar Children no React com TypeScript. ReactNode, ReactElement, PropsWithChildren... qual usar? Descubra a diferenca e quando aplicar cada tipo de children no React com TypeScript.
A diferenca entre ReactNode, ReactElement e JSX.Element
Galera, essa e a duvida mais comum. O React tem tres tipos pra representar "coisas que podem ser renderizadas" e cada um tem um escopo diferente. Vamos direto ao ponto:
ReactNode e o tipo mais amplo. Aceita elementos JSX, strings, numeros, booleanos, null, undefined e arrays de tudo isso. Na maioria dos casos, e o que voce quer pra children. ReactElement e mais restrito: so aceita elementos JSX criados com React.createElement. Nao aceita string, numero ou null. JSX.Element e basicamente igual ao ReactElement mas com props tipados como any.
import { ReactNode, ReactElement } from 'react';
// ReactNode aceita TUDO que pode ser renderizado
type ReactNode =
| ReactElement
| string
| number
| boolean
| null
| undefined
| Iterable<ReactNode>;
// ReactElement: so elementos JSX
interface ReactElement {
type: string | ComponentType;
props: object;
key: string | null;
}
// Comparacao pratica
const a: ReactNode = 'texto'; // OK
const b: ReactNode = 42; // OK
const c: ReactNode = null; // OK
const d: ReactNode = <div />; // OK
const e: ReactElement = 'texto'; // ERRO!
const f: ReactElement = 42; // ERRO!
const g: ReactElement = <div />; // OK
Passo a passo: tipando children corretamente
Exemplos praticos de tipagem de children
Componente wrapper com ReactNode
O caso mais comum: um componente que envolve outros com algum estilo ou logica. Da pra receber qualquer conteudo renderizavel.
import { ReactNode } from 'react';
// Jeito explicito: declarando children na interface
interface CardProps {
title: string;
children: ReactNode;
variant?: 'default' | 'highlighted';
}
function Card({ title, children, variant = 'default' }: CardProps) {
return (
<div className={`card card--${variant}`}>
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Todos esses usos sao validos:
<Card title="Info">Texto simples</Card>
<Card title="Info">{42}</Card>
<Card title="Info"><p>Elemento JSX</p></Card>
<Card title="Info">
<p>Multiplos</p>
<p>Elementos</p>
</Card>
PropsWithChildren: o atalho do React
Se voce ja tem uma interface de props e quer adicionar children sem digitar de novo, PropsWithChildren resolve. Ele adiciona children?: ReactNode automaticamente.
import { PropsWithChildren } from 'react';
// Sem PropsWithChildren
interface LayoutProps {
sidebar: ReactNode;
children: ReactNode;
}
// Com PropsWithChildren (resultado identico)
type LayoutProps2 = PropsWithChildren<{
sidebar: ReactNode;
}>;
function Layout({ sidebar, children }: LayoutProps2) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
// PropsWithChildren sem props extras
type WrapperProps = PropsWithChildren;
function Wrapper({ children }: WrapperProps) {
return <div className="wrapper">{children}</div>;
}
ReactElement: quando precisa restringir
Em alguns casos voce quer que o componente receba so elementos JSX, nao texto solto. Um exemplo: componente de Tabs que precisa iterar sobre os filhos e ler suas props.
import { ReactElement, Children, isValidElement, cloneElement } from 'react';
interface TabProps {
label: string;
children: ReactNode;
}
interface TabsProps {
children: ReactElement<TabProps> | ReactElement<TabProps>[];
activeIndex: number;
}
function Tabs({ children, activeIndex }: TabsProps) {
const tabs = Children.toArray(children)
.filter(isValidElement) as ReactElement<TabProps>[];
return (
<div>
<nav>
{tabs.map((tab, i) => (
<button key={i} className={i === activeIndex ? 'active' : ''}>
{tab.props.label}
</button>
))}
</nav>
<div>{tabs[activeIndex]}</div>
</div>
);
}
// Uso correto
<Tabs activeIndex={0}>
<Tab label="Geral">Conteudo geral</Tab>
<Tab label="Config">Configuracoes</Tab>
</Tabs>
// Erro: texto nao e ReactElement
<Tabs activeIndex={0}>
Texto solto {/* TypeScript Error! */}
</Tabs>
Render props: children como funcao
Render props e um padrao poderoso onde children e uma funcao. O TypeScript garante que o consumidor passe uma funcao com a assinatura certa e use os parametros corretamente.
import { useState, ReactNode } from 'react';
// Children como funcao tipada
interface ToggleProps {
children: (props: {
isOn: boolean;
toggle: () => void;
}) => ReactNode;
}
function Toggle({ children }: ToggleProps) {
const [isOn, setIsOn] = useState(false);
const toggle = () => setIsOn(prev => !prev);
return <>{children({ isOn, toggle })}</>;
}
// Uso: TypeScript valida os parametros da funcao
<Toggle>
{({ isOn, toggle }) => (
<button onClick={toggle}>
{isOn ? 'Ligado' : 'Desligado'}
</button>
)}
</Toggle>
// Render prop generica com dados
interface FetchRenderProps<T> {
url: string;
children: (props: {
data: T | null;
loading: boolean;
error: string | null;
}) => ReactNode;
}
function Fetch<T>({ url, children }: FetchRenderProps<T>) {
// ... logica de fetch
return <>{children({ data: null, loading: true, error: null })}</>;
}
Erros comuns ao tipar children
Cuidado com essas armadilhas
Usar JSX.Element como tipo de children e um erro sutil: ele nao aceita strings nem numeros, entao <Componente>Ola</Componente> da erro de tipo. Outro problema classico: esquecer que PropsWithChildren faz children opcional por padrao — se o componente precisa de children, declare explicitamente sem o ? na interface.
// ERRADO: JSX.Element rejeita texto
interface BadProps {
children: JSX.Element;
}
function Bad({ children }: BadProps) { return <div>{children}</div>; }
<Bad>Texto</Bad> // ERRO: string nao e JSX.Element
// CERTO: ReactNode aceita tudo
interface GoodProps {
children: ReactNode;
}
// ERRADO: PropsWithChildren faz children opcional
type OptionalKids = PropsWithChildren<{ title: string }>;
// children?: ReactNode (pode ser undefined!)
// CERTO: declare children obrigatorio
interface RequiredKids {
title: string;
children: ReactNode; // sem ?, sempre obrigatorio
}
// ERRADO: tipar children como string[]
interface ListProps {
children: string[]; // Muito restritivo
}
// CERTO: aceitar ReactNode e tratar no componente
interface ListProps2 {
children: ReactNode;
}
Checklist: children tipado corretamente
Checklist Final
Leve sua tipagem React pro proximo nivel
Projeto completo com TypeScript
Saber tipar children direito e o que torna seus componentes reutilizaveis de verdade. No CrazyStack voce constroi componentes como esses dentro de um projeto real completo, usando TypeScript, React e Node.js. Do layout ate a API, tudo com tipagem profissional.