cómo renderizar componentes de reacción usando map y join

102

Tengo un componente que mostrará Array of String. El código se ve así.

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

Funciona perfectamente bien. es decir, si props.data = ['tom', 'jason', 'chris'] El resultado renderizado en la página sería tomjasonchris

Luego, quiero unir todos los nombres usando una coma, así que cambio el código a

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

Sin embargo, el resultado renderizado es [Objeto], [Objeto], [Objeto].

No sé cómo interpretar el objeto para que se convierta en componentes de reacción que se renderizarán. Cualquier sugerencia ?

Xiaohe Dong
fuente

Respuestas:

147

Una solución simple es usar reduce()sin segundo argumento y sin difundir el resultado anterior:

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

Sin segundo argumento, reduce()se iniciará en el índice 1 en lugar de 0, y reaccionar es perfectamente feliz con matrices anidadas.

Como se dijo en los comentarios, solo desea usar esto para matrices con al menos un elemento, porque reduce()sin un segundo argumento arrojará una matriz vacía. Normalmente, esto no debería ser un problema, ya que desea mostrar un mensaje personalizado que diga algo como 'esto está vacío' para las matrices vacías de todos modos.

Actualización para TypeScript

Puede usar esto en TypeScript (sin type-unsafe any) con un React.ReactNodeparámetro de tipo en .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
fuente
3
If the array is empty and no initialValue was provided, TypeError would be thrown.. por lo que no funcionará para una matriz vacía.
Eugene Krevenets
6
Tenga en cuenta también que esto anida recursivamente la prevmatriz. por ejemplo, se [1, 2, 3, 4]convierte en [[[1,",",2],",",3],",",4].
Tamlyn
2
@Tamlyn: ¿Crees que mi mención original de tu punto en la respuesta ('React está perfectamente contento con las matrices anidadas') es lo suficientemente clara o debería ampliar esto?
Maarten ter Horst
1
¿Alguien hizo que esta solución funcionara con React + TypeScript?
Matt Dell
1
@Jstuff Terminé teniendo que usar alguno. .reduce((prev: JSX.Element, current: JSX.Element): any => [prev, (<span>&nbsp;</span>), current])
Matt Dell
26

Puede utilizar reducepara combinar varios elementos de una matriz:

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

Esto inicializa el acumulador con nulo, por lo que podemos envolver el primer elemento en una matriz. Para cada elemento siguiente en la matriz, construimos una nueva matriz que contiene todos los elementos anteriores usando ...-operator, agregamos el separador y luego el siguiente elemento.

Array.prototype.reduce ()

devsnd
fuente
2
gracias, exactamente lo que necesitaba. Puede eliminar la verificación nula y el ternario inicializando el acumulador como una matriz vacía
Larry
7
@Larry, si eliminas la verificación nula y el ternario, y pasas [] como inicializador, ¿cómo evitas una coma inicial? ['a'].reduce((a, e) => [...a, ',', e],[])rendimientos [",", "a"]. No pasar ningún inicializador funciona a menos que la matriz esté vacía, en cuyo caso devuelve un TypeError.
Guy
@ Guy, tienes toda la razón: mi error fue que estaba uniendo valores con espacios y faltaba el intervalo vacío en la salida renderizada
Larry
12

Actualización con React 16: ahora es posible renderizar cadenas directamente, por lo que puede simplificar su código eliminando todas las <span>etiquetas inútiles .

const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);
Médula
fuente
8

La respuesta aceptada en realidad devuelve una matriz de matrices porque prevserá una matriz cada vez. React es lo suficientemente inteligente como para hacer que esto funcione, pero es propenso a causar problemas en el futuro, como romper el algoritmo diferente de Reacts al dar claves para cada resultado del mapa.

La nueva React.Fragmentfunción nos permite hacer esto de una manera fácil de entender sin los problemas subyacentes.

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

Con React.Fragmentsimplemente podemos colocar el separador ,fuera del HTML devuelto y React no se quejará.

Jstuff
fuente
2
Solución impresionante, pero debe eliminar la coma para el último elemento de la matriz
indapublic
Siempre puedes usar un torneado y el índice para resolver eso.
Jstuff
2
Puede eliminar la última coma con esta modificación: <React.Fragment key = {index}> <span> {t} </span> {index === this.props.data.length - 1? '': ','} </React.Fragment>
JohnP
7

lo <span>{t}</span>que está devolviendo es un objeto, no una cadena. Consulte los documentos de reacción al respecto https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform

Al usar .join()en la matriz devuelta de map, se une a la matriz de objetos.[object Object], ...

Puede poner la coma dentro del <span></span>para que se represente de la manera que desee.

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

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

oobgam
fuente
3

Mi variante:

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

Si solo quiero representar una matriz de componentes separados por comas, generalmente encuentro reducedemasiado detallado. Una solución más corta en tales casos es

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

{index > 0 && ', '} representará una coma seguida de un espacio delante de todos los elementos de la matriz excepto el primero.

Si desea separar el penúltimo elemento y el último por otra cosa, diga la cadena ' and ', puede reemplazar {index > 0 && ', '}con

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

Usando es6 podrías hacer algo como:

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

Y luego ejecuta:

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

Utilice una matriz anidada para mantener "," fuera.

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

Optimícelo guardando los datos en una matriz y modifique el último elemento en lugar de verificar si es su último elemento todo el tiempo.

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

Esto funcionó para mí:

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

Como mencionó Pith , React 16 le permite usar cadenas directamente, por lo que ya no es necesario envolver las cadenas en etiquetas span. Sobre la base de la respuesta de Maarten , si también desea lidiar con un mensaje personalizado de inmediato (y evitar arrojar un error en una matriz vacía), puede dirigir la operación con una declaración if ternaria en la propiedad de longitud en la matriz. Eso podría verse así:

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
fuente
0

Esto debería devolver una matriz plana. Maneje el caso con el primer objeto no iterable proporcionando una matriz vacía inicial y filtre la primera coma no necesaria del resultado

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

¿Soy el único que piensa que hay mucha propagación y anidamiento innecesarios en las respuestas aquí? Puntos por ser concisos, claro, pero deja la puerta abierta a problemas de escala o React cambiando la forma en que tratan con matrices / Fragmentos anidados.

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>

Una matriz, sin anidamiento. Tampoco hay un método atractivo de encadenamiento, pero si se encuentra haciendo esto con frecuencia, siempre puede agregarlo al prototipo de matriz o envolverlo en una función que también tome una devolución de llamada de mapeo para hacerlo todo de una vez.

Lokasenna
fuente
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
fuente