comment rendre les composants de réaction à l'aide de la carte et de la jointure

102

J'ai un composant qui va afficher Array of String. Le code ressemble à ceci.

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

Cela fonctionne parfaitement bien. ie si props.data = ['tom', 'jason', 'chris'] Le résultat rendu dans la page serait tomjasonchris

Ensuite, je veux joindre tous les noms en utilisant une virgule, donc je change le code en

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

Cependant, le résultat rendu est [Objet], [Objet], [Objet].

Je ne sais pas comment interpréter l'objet pour devenir des composants de réaction à rendre. Toute suggestion ?

Xiaohe Dong
la source

Réponses:

150

Une solution simple consiste à utiliser reduce()sans second argument et sans étaler le résultat précédent:

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

Sans deuxième argument, reduce()va commencer à l' index 1 au lieu de 0 et est parfaitement heureux React avec des tableaux imbriqués.

Comme indiqué dans les commentaires, vous ne souhaitez l'utiliser que pour les tableaux avec au moins un élément, car reduce()sans deuxième argument, il sera lancé avec un tableau vide. Normalement, cela ne devrait pas être un problème, puisque vous voulez afficher un message personnalisé disant quelque chose comme «ceci est vide» pour les tableaux vides de toute façon.

Mise à jour pour Typescript

Vous pouvez l'utiliser dans Typescript (sans type-unsafe any) avec un React.ReactNodeparamètre de type sur .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
la source
3
If the array is empty and no initialValue was provided, TypeError would be thrown.. donc cela ne fonctionnera pas pour un tableau vide.
Eugene Krevenets
6
Notez également que cela imbrique récursivement le prevtableau. [1, 2, 3, 4]devient par exemple [[[1,",",2],",",3],",",4].
Tamlyn
2
@Tamlyn: Pensez-vous que ma mention originale de votre point dans la réponse («React est parfaitement satisfait des tableaux imbriqués») est assez claire ou devrais-je élaborer là-dessus?
Maarten ter Horst
1
Quelqu'un a fait fonctionner cette solution avec React + TypeScript?
Matt Dell
1
@Jstuff J'ai fini par devoir en utiliser. .reduce((prev: JSX.Element, current: JSX.Element): any => [prev, (<span>&nbsp;</span>), current])
Matt Dell
26

Vous pouvez utiliser reducepour combiner plusieurs éléments d'un tableau:

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

Cela initialise l'accumulateur avec null, nous pouvons donc envelopper le premier élément dans un tableau. Pour chaque élément suivant du tableau, nous construisons un nouveau tableau qui contient tous les éléments précédents à l'aide de ...-operator, ajoutons le séparateur, puis l'élément suivant.

Array.prototype.reduce ()

devsnd
la source
2
merci, exactement ce dont j'avais besoin. Vous pouvez supprimer la vérification nulle et le ternaire en initialisant l'accumulateur en tant que tableau vide
Larry
7
@Larry si vous supprimez le contrôle nul et le ternaire, et passez [] comme initialiseur, comment éviter une virgule au début? ['a'].reduce((a, e) => [...a, ',', e],[])rendements [",", "a"]. Ne pas transmettre d'initialiseur fonctionne à moins que le tableau ne soit vide, auquel cas il renvoie une TypeError.
Guy le
@Guy, vous avez tout à fait raison - mon erreur était que je joignais des valeurs avec des espaces et que je manquais la plage vide dans la sortie rendue
Larry
12

Mise à jour avec React 16: Il est désormais possible de rendre les chaînes directement, vous pouvez donc simplifier votre code en supprimant toutes les <span>balises inutiles .

const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);
Moelle
la source
8

La réponse acceptée renvoie en fait un tableau de tableaux car ce prevsera un tableau à chaque fois. React est assez intelligent pour que cela fonctionne, mais est-il susceptible de causer des problèmes à l'avenir, tels que la rupture de l'algorithme différent de Reacts lors de la fourniture de clés à chaque résultat de la carte.

La nouvelle React.Fragmentfonctionnalité nous permet de le faire d'une manière facile à comprendre sans les problèmes sous-jacents.

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

Avec React.Fragmentnous pouvons simplement placer le séparateur en ,dehors du HTML renvoyé et React ne se plaindra pas.

Jstuff
la source
2
Solution géniale, mais vous devez supprimer la virgule pour le dernier élément du tableau
indapublic
Vous pouvez toujours utiliser un service de tournage et l'index pour résoudre ce problème.
Jstuff
2
Vous pouvez supprimer la dernière virgule avec cette modification: <React.Fragment key = {index}> <span> {t} </span> {index === this.props.data.length - 1? '': ','} </React.Fragment>
JohnP
7

le que <span>{t}</span>vous renvoyez est un objet, pas une chaîne. Consultez les documents de réaction à ce sujet https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform

En utilisant .join()sur le tableau renvoyé de map, vous joignez le tableau d'objets.[object Object], ...

Vous pouvez mettre la virgule à l'intérieur du <span></span>afin qu'il soit rendu comme vous le souhaitez.

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

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

oobgam
la source
3

Ma variante:

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

Si je veux juste rendre un tableau de composants séparés par des virgules, je trouve généralement reducetrop verbeux. Une solution plus courte dans de tels cas est

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

{index > 0 && ', '} rendra une virgule suivie d'un espace devant tous les éléments du tableau sauf le premier.

Si vous voulez séparer l'avant-dernier élément et le dernier par autre chose, dites la chaîne ' and ', vous pouvez le remplacer {index > 0 && ', '}par

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

En utilisant es6, vous pouvez faire quelque chose comme:

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

Et puis exécutez:

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

Utilisez un tableau imbriqué pour garder "," à l'extérieur.

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

Optimisez-le en enregistrant les données dans un tableau et en modifiant le dernier élément au lieu de vérifier si son dernier élément tout le temps.

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

Cela a fonctionné pour moi:

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

Comme mentionné par Pith , React 16 vous permet d'utiliser des chaînes directement, de sorte que l'encapsulation des chaînes dans des balises span n'est plus nécessaire. En vous basant sur la réponse de Maarten , si vous souhaitez également traiter immédiatement un message personnalisé (et éviter de lancer une erreur sur un tableau vide), vous pouvez diriger l'opération avec une instruction if ternaire sur la propriété length du tableau. Cela pourrait ressembler à ceci:

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
la source
0

Cela devrait renvoyer un tableau plat. Gérer le cas avec le premier objet non itérable en fournissant un tableau vide initial et filtre la première virgule non nécessaire du résultat

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

Suis-je le seul à penser qu'il y a beaucoup de propagation et de nidification inutiles dans les réponses ici? Il convient d'être concis, bien sûr, mais cela laisse la porte ouverte aux problèmes d'échelle ou de réaction modifiant la façon dont ils traitent les tableaux / fragments imbriqués.

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>

Une baie, pas d'imbrication. Pas de chaînage de méthode sexy non plus, mais si vous vous retrouvez à faire cela souvent, vous pouvez toujours l'ajouter au prototype du tableau ou l'envelopper dans une fonction qui prend également un rappel de mappage pour tout faire en une seule fois.

Lokasenna
la source
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
la source