Pitfall

Usar Children é incomum e pode levar a código frágil. Veja alternativas comuns.

Children permite que você manipule e transforme o JSX que você recebeu como a children prop.

const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);

Referência

Children.count(children)

Chame Children.count(children) para contar o número de filhos na estrutura de dados children.

import { Children } from 'react';

function RowList({ children }) {
return (
<>
<h1>Total de linhas: {Children.count(children)}</h1>
...
</>
);
}

Veja mais exemplos abaixo.

Parâmetros

  • children: O valor da children prop recebido pelo seu componente.

Retorna

O número de nós dentro desses children.

Ressalvas

  • Nós vazios (null, undefined e Booleans), strings, números e elementos React contam como nós individuais. Arrays não contam como nós individuais, mas seus filhos contam. A travessia não vai mais fundo do que elementos React: eles não são renderizados, e seus filhos não são atravessados. Fragmentos não são atravessados.

Children.forEach(children, fn, thisArg?)

Chame Children.forEach(children, fn, thisArg?) para executar algum código para cada filho na estrutura de dados children.

import { Children } from 'react';

function SeparatorList({ children }) {
const result = [];
Children.forEach(children, (child, index) => {
result.push(child);
result.push(<hr key={index} />);
});
// ...

Veja mais exemplos abaixo.

Parâmetros

  • children: O valor da children prop recebido pelo seu componente.
  • fn: A função que você deseja executar para cada filho, semelhante ao callback do método forEach de array. Ela será chamada com o filho como o primeiro argumento e seu índice como o segundo argumento. O índice começa em 0 e incrementa a cada chamada.
  • opcional thisArg: O this value com o qual a função fn deve ser chamada. Se omitido, é undefined.

Retorna

Children.forEach retorna undefined.

Ressalvas

  • Nós vazios (null, undefined e Booleans), strings, números e elementos React contam como nós individuais. Arrays não contam como nós individuais, mas seus filhos contam. A travessia não vai mais fundo do que elementos React: eles não são renderizados, e seus filhos não são atravessados. Fragmentos não são atravessados.

Children.map(children, fn, thisArg?)

Chame Children.map(children, fn, thisArg?) para mapear ou transformar cada filho na estrutura de dados children.

import { Children } from 'react';

function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}

Veja mais exemplos abaixo.

Parâmetros

  • children: O valor da children prop recebido pelo seu componente.
  • fn: A função de mapeamento, semelhante ao callback do método map de array. Ela será chamada com o filho como o primeiro argumento e seu índice como o segundo argumento. O índice começa em 0 e incrementa a cada chamada. Você precisa retornar um nó React desta função. Isso pode ser um nó vazio (null, undefined ou um Boolean), uma string, um número, um elemento React ou um array de outros nós React.
  • opcional thisArg: O this value com o qual a função fn deve ser chamada. Se omitido, é undefined.

Retorna

Se children é null ou undefined, retorna o mesmo valor.

Caso contrário, retorna um array plano consistindo dos nós que você retornou da função fn. O array retornado conterá todos os nós que você retornou, exceto null e undefined.

Ressalvas

  • Nós vazios (null, undefined e Booleans), strings, números e elementos React contam como nós individuais. Arrays não contam como nós individuais, mas seus filhos contam. A travessia não vai mais fundo do que elementos React: eles não são renderizados, e seus filhos não são atravessados. Fragmentos não são atravessados.

  • Se você retornar um elemento ou um array de elementos com chaves de fn, as chaves dos elementos retornados serão automaticamente combinadas com a chave do item original correspondente de children. Quando você retorna múltiplos elementos de fn em um array, suas chaves só precisam ser únicas localmente entre si.


Children.only(children)

Chame Children.only(children) para afirmar que children representa um único elemento React.

function Box({ children }) {
const element = Children.only(children);
// ...

Parâmetros

  • children: O valor da children prop recebido pelo seu componente.

Retorna

Se children é um elemento válido, retorna esse elemento.

Caso contrário, lança um erro.

Ressalvas

  • Este método sempre lança uma exceção se você passar um array (como o valor de retorno de Children.map) como children. Em outras palavras, ele impõe que children seja um único elemento React, não que seja um array com um único elemento.

Children.toArray(children)

Chame Children.toArray(children) para criar um array a partir da estrutura de dados children.

import { Children } from 'react';

export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...

Parâmetros

  • children: O valor da children prop recebido pelo seu componente.

Retorna

Retorna um array plano de elementos em children.

Ressalvas

  • Nós vazios (null, undefined e Booleans) serão omitidos no array retornado. As chaves dos elementos retornados serão calculadas a partir das chaves dos elementos originais e seu nível de aninhamento e posição. Isso garante que achatar o array não introduza mudanças no comportamento.

Uso

Transformando filhos

Para transformar o JSX de filhos que seu componente recebe como a children prop, chame Children.map:

import { Children } from 'react';

function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}

No exemplo acima, o RowList envolve cada filho que recebe em um contêiner <div className="Row">. Por exemplo, digamos que o componente pai passe três tags <p> como a children prop para RowList:

<RowList>
<p>Este é o primeiro item.</p>
<p>Este é o segundo item.</p>
<p>Este é o terceiro item.</p>
</RowList>

Então, com a implementação do RowList acima, o resultado renderizado final ficará assim:

<div className="RowList">
<div className="Row">
<p>Este é o primeiro item.</p>
</div>
<div className="Row">
<p>Este é o segundo item.</p>
</div>
<div className="Row">
<p>Este é o terceiro item.</p>
</div>
</div>

Children.map é semelhante a transformar arrays com map(). A diferença é que a estrutura de dados children é considerada opaca. Isso significa que mesmo que às vezes seja um array, você não deve assumir que é um array ou qualquer outro tipo de dado específico. Por isso, você deve usar Children.map se precisar transformá-lo.

import { Children } from 'react';

export default function RowList({ children }) {
  return (
    <div className="RowList">
      {Children.map(children, child =>
        <div className="Row">
          {child}
        </div>
      )}
    </div>
  );
}

Deep Dive

Por que a prop children nem sempre é um array?

No React, a prop children é considerada uma estrutura de dados opaca. Isso significa que você não deve depender de como ela está estruturada. Para transformar, filtrar ou contar filhos, você deve usar os métodos Children.

Na prática, a estrutura de dados children é frequentemente representada como um array internamente. No entanto, se houver apenas um único filho, o React não criará um array extra, pois isso levaria a uma sobrecarga desnecessária de memória. Enquanto você usar os métodos Children em vez de introspectar diretamente a prop children, seu código não quebrará mesmo que o React mude como a estrutura de dados é realmente implementada.

Mesmo quando children é um array, Children.map tem um comportamento especial útil. Por exemplo, Children.map combina as chaves nos elementos retornados com as chaves nos children que você passou para ele. Isso garante que os filhos JSX originais não “percam” chaves, mesmo que sejam envoltos como no exemplo acima.

Pitfall

A estrutura de dados children não inclui a saída renderizada dos componentes que você passa como JSX. No exemplo abaixo, os children recebidos pelo RowList contêm apenas dois itens, em vez de três:

  1. <p>Este é o primeiro item.</p>
  2. <MoreRows />

É por isso que apenas duas envoltórias de linha são geradas neste exemplo:

import RowList from './RowList.js';

export default function App() {
  return (
    <RowList>
      <p>Este é o primeiro item.</p>
      <MoreRows />
    </RowList>
  );
}

function MoreRows() {
  return (
    <>
      <p>Este é o segundo item.</p>
      <p>Este é o terceiro item.</p>
    </>
  );
}

Não há como obter a saída renderizada de um componente interno como <MoreRows /> ao manipular children. É por isso que geralmente é melhor usar uma das soluções alternativas.


Executando algum código para cada filho

Chame Children.forEach para iterar sobre cada filho na estrutura de dados children. Ele não retorna nenhum valor e é semelhante ao método forEach de array. Você pode usá-lo para executar lógica personalizada, como construir seu próprio array.

import { Children } from 'react';

export default function SeparatorList({ children }) {
  const result = [];
  Children.forEach(children, (child, index) => {
    result.push(child);
    result.push(<hr key={index} />);
  });
  result.pop(); // Remove o último separador
  return result;
}

Pitfall

Como mencionado anteriormente, não há como obter a saída renderizada de um componente interno ao manipular children. É por isso que geralmente é melhor usar uma das soluções alternativas.


Contando filhos

Chame Children.count(children) para calcular o número de filhos.

import { Children } from 'react';

export default function RowList({ children }) {
  return (
    <div className="RowList">
      <h1 className="RowListHeader">
        Total de linhas: {Children.count(children)}
      </h1>
      {Children.map(children, child =>
        <div className="Row">
          {child}
        </div>
      )}
    </div>
  );
}

Pitfall

Como mencionado anteriormente, não há como obter a saída renderizada de um componente interno ao manipular children. É por isso que geralmente é melhor usar uma das soluções alternativas.


Convertendo filhos em um array

Chame Children.toArray(children) para transformar a estrutura de dados children em um array JavaScript regular. Isso permite que você manipule o array com métodos de array incorporados como filter, sort ou reverse.

import { Children } from 'react';

export default function ReversedList({ children }) {
  const result = Children.toArray(children);
  result.reverse();
  return result;
}

Pitfall

Como mencionado anteriormente, não há como obter a saída renderizada de um componente interno ao manipular children. É por isso que geralmente é melhor usar uma das soluções alternativas.


Alternativas

Note

Esta seção descreve alternativas à API Children (com C maiúsculo) que é importada assim:

import { Children } from 'react';

Não confunda isso com usar a prop children (com c minúsculo), que é boa e incentivada.

Expondo múltiplos componentes

Manipular filhos com os métodos Children muitas vezes leva a código frágil. Quando você passa filhos para um componente em JSX, geralmente não espera que o componente manipule ou transforme os filhos individuais.

Quando puder, tente evitar usar os métodos Children. Por exemplo, se você quiser que cada filho de RowList seja envolvido em <div className="Row">, exporte um componente Row e envolva manualmente cada linha assim:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList>
      <Row>
        <p>Este é o primeiro item.</p>
      </Row>
      <Row>
        <p>Este é o segundo item.</p>
      </Row>
      <Row>
        <p>Este é o terceiro item.</p>
      </Row>
    </RowList>
  );
}

Diferente de usar Children.map, essa abordagem não envolve automaticamente cada filho. No entanto, essa abordagem tem um benefício significativo comparado ao exemplo anterior com Children.map porque funciona mesmo se você continuar extraindo mais componentes. Por exemplo, ainda funciona se você extrair seu próprio componente MoreRows:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList>
      <Row>
        <p>Este é o primeiro item.</p>
      </Row>
      <MoreRows />
    </RowList>
  );
}

function MoreRows() {
  return (
    <>
      <Row>
        <p>Este é o segundo item.</p>
      </Row>
      <Row>
        <p>Este é o terceiro item.</p>
      </Row>
    </>
  );
}

Isso não funcionaria com Children.map porque “veria” <MoreRows /> como um filho único (e uma única linha).


Aceitando um array de objetos como uma prop

Você também pode passar explicitamente um array como uma prop. Por exemplo, este RowList aceita um array rows como uma prop:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList rows={[
      { id: 'first', content: <p>Este é o primeiro item.</p> },
      { id: 'second', content: <p>Este é o segundo item.</p> },
      { id: 'third', content: <p>Este é o terceiro item.</p> }
    ]} />
  );
}

Como rows é um array JavaScript regular, o componente RowList pode usar métodos de array incorporados como map nele.

Esse padrão é especialmente útil quando você deseja poder passar mais informações como dados estruturados junto com os filhos. No exemplo abaixo, o componente TabSwitcher recebe um array de objetos como a prop tabs:

import TabSwitcher from './TabSwitcher.js';

export default function App() {
  return (
    <TabSwitcher tabs={[
      {
        id: 'first',
        header: 'Primeiro',
        content: <p>Este é o primeiro item.</p>
      },
      {
        id: 'second',
        header: 'Segundo',
        content: <p>Este é o segundo item.</p>
      },
      {
        id: 'third',
        header: 'Terceiro',
        content: <p>Este é o terceiro item.</p>
      }
    ]} />
  );
}

Diferente de passar os filhos como JSX, essa abordagem permite que você associe alguns dados extras como header com cada item. Como você está trabalhando diretamente com os tabs, e este é um array, você não precisa dos métodos Children.


Chamando uma função de renderização para personalizar a renderização

Em vez de produzir JSX para cada item único, você também pode passar uma função que retorna JSX e chamar essa função quando necessário. Neste exemplo, o componente App passa uma função renderContent para o componente TabSwitcher. O componente TabSwitcher chama renderContent apenas para a aba selecionada:

import TabSwitcher from './TabSwitcher.js';

export default function App() {
  return (
    <TabSwitcher
      tabIds={['first', 'second', 'third']}
      getHeader={tabId => {
        return tabId[0].toUpperCase() + tabId.slice(1);
      }}
      renderContent={tabId => {
        return <p>Este é o item {tabId}.</p>;
      }}
    />
  );
}

Uma prop como renderContent é chamada de render prop porque é uma prop que especifica como renderizar uma parte da interface do usuário. No entanto, não há nada de especial sobre isso: é uma prop regular que acontece de ser uma função.

Render props são funções, então você pode passar informações para elas. Por exemplo, este componente RowList passa o id e o index de cada linha para o render prop renderRow, que usa index para destacar linhas pares:

import { RowList, Row } from './RowList.js';

export default function App() {
  return (
    <RowList
      rowIds={['first', 'second', 'third']}
      renderRow={(id, index) => {
        return (
          <Row isHighlighted={index % 2 === 0}>
            <p>Este é o item {id}.</p>
          </Row> 
        );
      }}
    />
  );
}

Este é outro exemplo de como componentes pai e filho podem cooperar sem manipular os filhos.


Solução de Problemas

Eu passo um componente personalizado, mas os métodos Children não mostram seu resultado de renderização

Suponha que você passe dois filhos para RowList assim:

<RowList>
<p>Primeiro item</p>
<MoreRows />
</RowList>

Se você fizer Children.count(children) dentro de RowList, obterá 2. Mesmo que MoreRows renderize 10 itens diferentes, ou se retornar null, Children.count(children) ainda será 2. Do ponto de vista do RowList, ele só “vê” o JSX que recebeu. Ele não “vê” os internos do componente MoreRows.

A limitação torna difícil extrair um componente. É por isso que soluções alternativas são preferidas ao usar Children.