React JS - Uncaught TypeError: this.props.data.map n'est pas une fonction

112

Je travaille avec reactjs et je ne parviens pas à empêcher cette erreur lorsque je tente d'afficher des données JSON (à partir d'un fichier ou d'un serveur):

Uncaught TypeError: this.props.data.map is not a function

J'ai regardé:

Code de réaction lançant "TypeError: this.props.data.map n'est pas une fonction"

React.js this.props.data.map () n'est pas une fonction

Aucun de ces éléments ne m'a aidé à résoudre le problème. Après le chargement de ma page, je peux vérifier que this.data.props n'est pas indéfini (et a une valeur équivalente à l'objet JSON - peut appeler avec window.foo), il semble donc qu'il ne se charge pas à temps lorsqu'il est appelé par ConversationList. Comment m'assurer que la mapméthode fonctionne sur les données JSON et non sur une undefinedvariable?

var converter = new Showdown.converter();

var Conversation = React.createClass({
  render: function() {
    var rawMarkup = converter.makeHtml(this.props.children.toString());
    return (
      <div className="conversation panel panel-default">
        <div className="panel-heading">
          <h3 className="panel-title">
            {this.props.id}
            {this.props.last_message_snippet}
            {this.props.other_user_id}
          </h3>
        </div>
        <div className="panel-body">
          <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
        </div>
      </div>
    );
  }
});

var ConversationList = React.createClass({
  render: function() {

    window.foo            = this.props.data;
    var conversationNodes = this.props.data.map(function(conversation, index) {

      return (
        <Conversation id={conversation.id} key={index}>
          last_message_snippet={conversation.last_message_snippet}
          other_user_id={conversation.other_user_id}
        </Conversation>
      );
    });

    return (
      <div className="conversationList">
        {conversationNodes}
      </div>
    );
  }
});

var ConversationBox = React.createClass({
  loadConversationsFromServer: function() {
    return $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadConversationsFromServer();
    setInterval(this.loadConversationsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="conversationBox">
        <h1>Conversations</h1>
        <ConversationList data={this.state.data} />
      </div>
    );
  }
});

$(document).on("page:change", function() {
  var $content = $("#content");
  if ($content.length > 0) {
    React.render(
      <ConversationBox url="/conversations.json" pollInterval={20000} />,
      document.getElementById('content')
    );
  }
})

EDIT: ajout d'exemples de conversations.json

Remarque - l'appel this.props.data.conversationsrenvoie également une erreur:

var conversationNodes = this.props.data.conversations.map...

renvoie l'erreur suivante:

Uncaught TypeError: impossible de lire la propriété 'map' d'undefined

Voici conversations.json:

{"user_has_unread_messages":false,"unread_messages_count":0,"conversations":[{"id":18768,"last_message_snippet":"Lorem ipsum","other_user_id":10193}]}
pulsations de touches
la source
J'ai mis à jour la réponse. Aussi, quelques recommandations: ajoutez propTypes: {data: React.PropTypes.array} à ConversationList pour vérifier le type de données, et ajoutez un componentWillUnmount: fn () qui "clearInterval" l'intervalle dans ConversationBox.
user120242

Réponses:

135

La .mapfonction n'est disponible que sur la baie.
Il semble qu'il datane soit pas dans le format que vous attendez (il est {} mais vous attendez []).

this.setState({data: data});

devrait être

this.setState({data: data.conversations});

Vérifiez sur quel type "données" est défini et assurez-vous qu'il s'agit d'un tableau.

Code modifié avec quelques recommandations (validation propType et clearInterval):

var converter = new Showdown.converter();

var Conversation = React.createClass({
  render: function() {
    var rawMarkup = converter.makeHtml(this.props.children.toString());
    return (
      <div className="conversation panel panel-default">
        <div className="panel-heading">
          <h3 className="panel-title">
            {this.props.id}
            {this.props.last_message_snippet}
            {this.props.other_user_id}
          </h3>
        </div>
        <div className="panel-body">
          <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
        </div>
      </div>
    );
  }
});

var ConversationList = React.createClass({
 // Make sure this.props.data is an array
  propTypes: {
    data: React.PropTypes.array.isRequired
  },
  render: function() {

    window.foo            = this.props.data;
    var conversationNodes = this.props.data.map(function(conversation, index) {

      return (
        <Conversation id={conversation.id} key={index}>
          last_message_snippet={conversation.last_message_snippet}
          other_user_id={conversation.other_user_id}
        </Conversation>
      );
    });

    return (
      <div className="conversationList">
        {conversationNodes}
      </div>
    );
  }
});

var ConversationBox = React.createClass({
  loadConversationsFromServer: function() {
    return $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data.conversations});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },

 /* Taken from 
    https://facebook.github.io/react/docs/reusable-components.html#mixins
    clears all intervals after component is unmounted
  */
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.map(clearInterval);
  },

  componentDidMount: function() {
    this.loadConversationsFromServer();
    this.setInterval(this.loadConversationsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="conversationBox">
        <h1>Conversations</h1>
        <ConversationList data={this.state.data} />
      </div>
    );
  }
});

$(document).on("page:change", function() {
  var $content = $("#content");
  if ($content.length > 0) {
    React.render(
      <ConversationBox url="/conversations.json" pollInterval={20000} />,
      document.getElementById('content')
    );
  }
})
user120242
la source
33

ce qui a fonctionné pour moi est de convertir les données props.data en un tableau en utilisant data = Array.from(props.data); alors je pourrais utiliser la data.map()fonction

Vishal Seshagiri
la source
1
Mon type était déjà un tableau, par exemple, typeOf montrait la bonne chose et la longueur était correcte, mais l'erreur apparaissait. Cela a réglé le problème pour moi. Merci!
user1829319
10

Plus généralement, vous pouvez également convertir les nouvelles données en un tableau et utiliser quelque chose comme concat:

var newData = this.state.data.concat([data]);  
this.setState({data: newData})

Ce modèle est en fait utilisé dans l'application de démonstration ToDo de Facebook (voir la section «Une application») sur https://facebook.github.io/react/ .

Bresson
la source
1
C'est une astuce / astuce intéressante, mais je tiens à noter que cela masquerait le vrai problème et ne résoudrait pas OP. Dans le cas de l'OP, cela n'aiderait pas, car les données seraient toujours mal formées (pas ce qui était prévu). La conversation finirait probablement par être vide car tous les accessoires seraient nuls. Je recommanderais de ne pas utiliser cette astuce, sauf si vous êtes sûr que vos données sont au format souhaité, afin de ne pas vous retrouver avec un comportement inattendu lorsque les données renvoyées ne sont pas ce que vous pensiez qu'elles devraient être.
user120242
1

Vous n'avez pas besoin d'un tableau pour le faire.

var ItemNode = this.state.data.map(function(itemData) {
return (
   <ComponentName title={itemData.title} key={itemData.id} number={itemData.id}/>
 );
});
CodeGuru
la source
En quoi c'est différent frère?
Amar Pathak
1

Cela se produit parce que le composant est rendu avant l'arrivée des données asynchrones, vous devez contrôler avant le rendu.

Je l'ai résolu de cette manière:

render() {
    let partners = this.props && this.props.partners.length > 0 ?
        this.props.partners.map(p=>
            <li className = "partners" key={p.id}>
                <img src={p.img} alt={p.name}/> {p.name} </li>
        ) : <span></span>;

    return (
        <div>
            <ul>{partners}</ul>
        </div>
    );
}
  • La carte ne peut pas être résolue lorsque la propriété est nulle / non définie, j'ai donc d'abord effectué un contrôle

this.props && this.props.partners.length > 0 ?

JC
la source
0

Vous devez convertir l'objet en tableau pour utiliser la mapfonction:

const mad = Object.values(this.props.location.state);

this.props.location.stateest l'objet passé dans un autre composant.

ISRAEL ODUGUWA
la source
-2

essayez le componentDidMount()cycle de vie lors de la récupération des données

Arif Ramadhani
la source
5
Aussi, fournissez quelques explications, pourquoi cela fonctionne et comment?
Mittal Patel