React JS - Uncaught TypeError: this.props.data.map non è una funzione


110

Sto lavorando con reactjs e non riesco a prevenire questo errore quando provo a visualizzare i dati JSON (dal file o dal server):

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

Ho guardato:

Codice di reazione che lancia "TypeError: this.props.data.map non è una funzione"

React.js this.props.data.map () non è una funzione

Nessuno di questi mi ha aiutato a risolvere il problema. Dopo il caricamento della mia pagina, posso verificare che this.data.props non sia undefined (e abbia un valore equivalente all'oggetto JSON - può chiamare con window.foo), quindi sembra che non si stia caricando in tempo quando viene chiamato da ConversationList. Come posso assicurarmi che il mapmetodo funzioni sui dati JSON e non su una undefinedvariabile?

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: aggiunta di conversations.json di esempio

Nota: this.props.data.conversationsanche la chiamata restituisce un errore:

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

restituisce il seguente errore:

Uncaught TypeError: Impossibile leggere la proprietà 'map' di undefined

Ecco conversations.json:

{"user_has_unread_messages":false,"unread_messages_count":0,"conversations":[{"id":18768,"last_message_snippet":"Lorem ipsum","other_user_id":10193}]}

Ho aggiornato la risposta. Inoltre, alcuni consigli: aggiungi propTypes: {data: React.PropTypes.array} a ConversationList per verificare il tipo di dati, e aggiungi un componentWillUnmount: fn () che "clearInterval" l'intervallo in ConversationBox.
user120242

Risposte:


134

La .mapfunzione è disponibile solo su array.
Sembra che datanon sia nel formato che ti aspetti (è {} ma ti aspetti []).

this.setState({data: data});

dovrebbe essere

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

Controlla il tipo di "dati" impostato e assicurati che sia un array.

Codice modificato con alcune raccomandazioni (validazione propType e 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')
    );
  }
})

32

quello che ha funzionato per me è stato convertire il props.data in un array usando data = Array.from(props.data); quindi potrei usare la data.map()funzione


1
Il mio tipo era già un array, ad esempio typeOf mostrava la cosa giusta anche la lunghezza era corretta ma comunque l'errore appariva. Questo ha risolto il problema per me. Grazie!
user1829319

10

Più in generale, puoi anche convertire i nuovi dati in un array e usare qualcosa come concat:

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

Questo modello è effettivamente utilizzato nell'app demo ToDo di Facebook (vedere la sezione "Un'applicazione") su https://facebook.github.io/react/ .


1
Questo è un suggerimento / trucco accurato, ma voglio notare che questo maschererebbe il vero problema e non risolverebbe l'OP. Nel caso di OP ciò non sarebbe di aiuto, poiché i dati sarebbero ancora in formato non corretto (non ciò che era previsto). La conversazione molto probabilmente finirebbe vuota poiché tutti gli oggetti di scena sarebbero nulli. Consiglierei di non usare questo suggerimento, a meno che tu non sia sicuro che i tuoi dati siano nel formato che desideri, in modo da non finire con un comportamento imprevisto quando i dati restituiti non sono come pensavi che dovessero essere.
user120242

1

Non hai bisogno di un array per farlo.

var ItemNode = this.state.data.map(function(itemData) {
return (
   <ComponentName title={itemData.title} key={itemData.id} number={itemData.id}/>
 );
});

Com'è diverso fratello?
Amar Pathak

1

Succede perché il componente viene renderizzato prima dell'arrivo dei dati asincroni, dovresti controllare prima di eseguire il rendering.

L'ho risolto in questo modo:

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 mappa non può essere risolta quando la proprietà è null / undefined, quindi ho eseguito prima un controllo

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


0

È necessario convertire l'oggetto in un array per utilizzare la mapfunzione:

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

dove si this.props.location.statetrova l'oggetto passato in un altro componente.


-2

prova il componentDidMount()ciclo di vita durante il recupero dei dati


5
Inoltre, fornisci qualche spiegazione, perché funziona e come?
Mittal Patel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.