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}]}

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')
    );
  }
})

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


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/ .


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}/>
 );
});

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 ?


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.


-2

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


5
Aussi, fournissez quelques explications, pourquoi cela fonctionne et comment?
Mittal Patel
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.