como renderizar componentes de reação usando map e join

102

Eu tenho um componente que exibirá Array of String. O código se parece com isso.

React.createClass({
  render() {
     <div>
        this.props.data.map(t => <span>t</span>)
     </div>
  }
})

Está funcionando perfeitamente bem. ou seja, se props.data = ['tom', 'jason', 'chris'] O resultado renderizado na página seria tomjasonchris

Então, eu quero juntar todos os nomes usando vírgulas, então eu mudo o código para

this.props.data.map(t => <span>t</span>).join(', ')

No entanto, o resultado renderizado é [Object], [Object], [Object].

Eu não sei como interpretar objetos para se tornarem componentes de reação a serem renderizados. Alguma sugestão ?

Xiaohe Dong
fonte

Respostas:

147

Uma solução simples é usar reduce()sem segundo argumento e sem espalhar o resultado anterior:

class List extends React.Component {
  render() {
     <div>
        {this.props.data
          .map(t => <span>{t}</span>)
          .reduce((prev, curr) => [prev, ', ', curr])}
     </div>
  }
}

Sem segundo argumento, reduce()vai começar no índice 1 em vez de 0, e Reagir é perfeitamente feliz com matrizes aninhadas.

Como dito nos comentários, você deseja usar isso apenas para arrays com pelo menos um item, porque reduce()sem o segundo argumento vai lançar com um array vazio. Normalmente, isso não deve ser um problema, pois você deseja exibir uma mensagem personalizada dizendo algo como 'isto está vazio' para matrizes vazias de qualquer maneira.

Atualização para texto tipográfico

Você pode usar isso em Typescript (sem tipo-inseguro any) com um React.ReactNodeparâmetro de tipo em .map():

class List extends React.Component {
  render() {
     <div>
        {this.props.data
          .map<React.ReactNode>(t => <span>{t}</span>)
          .reduce((prev, curr) => [prev, ', ', curr])}
     </div>
  }
}
Maarten ter Horst
fonte
3
If the array is empty and no initialValue was provided, TypeError would be thrown.. então não funcionará para array vazio.
Eugene Krevenets
6
Observe também que isso aninha recursivamente a prevmatriz. por exemplo, [1, 2, 3, 4]torna-se [[[1,",",2],",",3],",",4].
Tamlyn
2
@Tamlyn: Você acha que minha menção original ao seu ponto na resposta ('React é perfeitamente feliz com matrizes aninhadas') é clara o suficiente ou devo explicar melhor?
Maarten ter Horst de
1
Alguém fez esta solução funcionar com React + TypeScript?
Matt Dell de
1
@Jstuff Acendi a necessidade de usar qualquer. .reduce((prev: JSX.Element, current: JSX.Element): any => [prev, (<span>&nbsp;</span>), current])
Matt Dell
26

Você pode usar reducepara combinar vários elementos de uma matriz:

React.createClass({
  render() {
     <div>
        this.props.data
        .map(t => <span>t</span>)
        .reduce((accu, elem) => {
            return accu === null ? [elem] : [...accu, ',', elem]
        }, null)
     </div>
  }
})

Isso inicializa o acumulador com nulo, portanto, podemos envolver o primeiro item em um array. Para cada elemento seguinte na matriz, construímos uma nova matriz que contém todos os elementos anteriores usando o ...-operator, adicionamos o separador e, em seguida, o próximo elemento.

Array.prototype.reduce ()

devsnd
fonte
2
obrigado, exatamente o que eu precisava. Você pode remover a verificação nula e ternária inicializando o acumulador como uma matriz vazia
Larry,
7
@Larry se você remover a verificação de nulo e o ternário e passar [] como o inicializador, como você evita uma vírgula inicial? ['a'].reduce((a, e) => [...a, ',', e],[])rendimentos [",", "a"]. Não passar nenhum inicializador funciona, a menos que o array esteja vazio; nesse caso, ele retorna um TypeError.
Guy
@Guy, você está absolutamente correto - meu erro foi que eu estava juntando valores com espaços e perdendo o intervalo vazio na saída renderizada
Larry
12

Atualização com React 16: agora é possível renderizar strings diretamente, então você pode simplificar seu código removendo todas as <span>tags inúteis .

const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);
Pith
fonte
8

A resposta aceita realmente retorna uma matriz de matrizes porque prev será uma matriz. O React é inteligente o suficiente para fazer isso funcionar, mas é propenso a causar problemas no futuro, como quebrar o algoritmo de comparação do Reacts ao fornecer as chaves para cada resultado do mapa.

O novo React.Fragmentrecurso nos permite fazer isso de maneira fácil de entender, sem os problemas subjacentes.

class List extends React.Component {
  render() {
     <div>
        {this.props.data
          .map((t, index) => 
            <React.Fragment key={index}>
              <span> {t}</span> ,
            </React.Fragment>
          )
      </div>
  }
}

Com React.Fragment, podemos simplesmente colocar o separador ,fora do HTML retornado e o React não reclamará.

Jstuff
fonte
2
Solução incrível, mas você precisa remover a vírgula para o último elemento da matriz
indapublic
Você sempre pode usar um torneiro e o índice para resolver isso.
Jstuff
2
Você pode remover a última vírgula com esta modificação: <React.Fragment key = {index}> <span> {t} </span> {index === this.props.data.length - 1? '': ''} </React.Fragment>
JohnP
7

o que <span>{t}</span>você está retornando é um objeto, não uma string. Verifique os documentos de reação sobre isso https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform

Ao usar .join()na matriz retornada de map, você está unindo a matriz de objetos.[object Object], ...

Você pode colocar a vírgula dentro do <span></span>para que seja renderizado da maneira que você deseja.

  render() {
    return (
      <div>
        { this.props.data.map(
            (t,i) => <span>{t}{ this.props.data.length - 1 === i ? '' : ','} </span>
          )
        }
      </div>
    )
  }

amostra: https://jsbin.com/xomopahalo/edit?html,js,output

oobgam
fonte
3

Minha variante:

{this.props.data
    .map(item => <span>{item}</span>)
    .map((item, index) => [index > 0 && ', ', item ])}
Fen1kz
fonte
2

Se eu só quiser renderizar uma matriz de componentes separados por vírgulas, geralmente acho reducemuito prolixo. Uma solução mais curta em tais casos é

{arr.map((item, index) => (
  <Fragment key={item.id}>
    {index > 0 && ', '}
    <Item {...item} />
  </Fragment>
))}

{index > 0 && ', '} irá renderizar uma vírgula seguida por um espaço na frente de todos os itens da matriz, exceto o primeiro.

Se você quiser separar o penúltimo item e o último por outra coisa, digamos a string ' and ', você pode substituir {index > 0 && ', '}por

{index > 0 && index !== arr.length - 1 && ', '}
{index === arr.length - 1 && ' and '}
Casimir
fonte
2

Usando o es6, você pode fazer algo como:

const joinComponents = (accumulator, current) => [
  ...accumulator, 
  accumulator.length ? ', ' : '', 
  current
]

E então execute:

listComponents
  .map(item => <span key={item.id}> {item.t} </span>)
  .reduce(joinComponents, [])
Marco Antônio
fonte
1

Use uma matriz aninhada para manter "," fora.

  <div>
      {this.props.data.map((element, index) => index == this.props.data.length - 1 ? <span key={index}>{element}</span> : [<span key={index}>{element}</span>, ", "])}
  </div>

Otimize-o salvando os dados na matriz e modifique o último elemento em vez de verificar se é o último elemento o tempo todo.

  let processedData = this.props.data.map((element, index) => [<span key={index}>{element}</span>, ", "])
  processedData [this.props.data.length - 1].pop()
  <div>
      {processedData}
  </div>
inc
fonte
0

Isso funcionou para mim:

    {data.map( ( item, i ) => {
                  return (
                      <span key={i}>{item.propery}</span>
                    )
                  } ).reduce( ( prev, curr ) => [ prev, ', ', curr ] )}
qin.ju
fonte
0

Conforme mencionado por Pith , o React 16 permite que você use strings diretamente, portanto, envolvê-las em tags span não é mais necessário. Com base na resposta de Maarten , se você também quiser lidar com uma mensagem personalizada imediatamente (e evitar lançar um erro no array vazio), você pode conduzir a operação com uma instrução if ternária na propriedade length do array. Isso poderia ser algo assim:

class List extends React.Component {
  const { data } = this.props;

  render() {
     <div>
        {data.length
          ? data.reduce((prev, curr) => [prev, ', ', curr])
          : 'No data in the array'
        }
     </div>
  }
}
Fredric Waadeland
fonte
0

Isso deve retornar uma matriz plana. Lida com o caso com o primeiro objeto não iterável, fornecendo uma matriz vazia inicial e filtra a primeira vírgula desnecessária do resultado

[{}, 'b', 'c'].reduce((prev, curr) => [...prev, ', ', curr], []).splice(1) // => [{}, 'b', 'c']
Calin Ortan
fonte
0

Eu sou o único que acha que há muita divulgação e aninhamento desnecessários acontecendo nas respostas aqui? Pontos por ser conciso, claro, mas deixa a porta aberta para questões de escala ou React, alterando a forma como lidam com matrizes / fragmentos aninhados.

const joinJsxArray = (arr, joinWith) => {
  if (!arr || arr.length < 2) { return arr; }

  const out = [arr[0]];
  for (let i = 1; i < arr.length; i += 1) {
    out.push(joinWith, arr[i]);
  }
  return out;
};

// render()
<div>
  {joinJsxArray(this.props.data.map(t => <span>t</span>), ', ')}
</div>

Uma matriz, sem aninhamento. Nenhum método de encadeamento sexy também, mas se você se pega fazendo isso com frequência, pode sempre adicioná-lo ao protótipo do array ou envolvê-lo em uma função que também receba um retorno de chamada de mapeamento para fazer tudo de uma vez.

Lokasenna
fonte
0
function YourComponent(props) {

  const criteria = [];

  if (something) {
    criteria.push(<strong>{ something }</strong>);
  }

  // join the jsx elements with `, `
  const elements = criteria.reduce((accu, elem) => {
    return accu === null ? [elem] : [...accu, ', ', elem]
  }, null);

  // render in a jsx friendly way
  return elements.map((el, index) => <React.Fragment key={ index }>{ el }</React.Fragment> );

}
ilovett
fonte