2015-05-09 12 views
40

Sto lavorando con reactjs e non riesco evitare questo errore quando si cerca di visualizzare i dati JSON (sia da file o server):Reagire JS - TypeError Uncaught: this.props.data.map non è una funzione

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

ho guardato:

React code throwing “TypeError: this.props.data.map is not a function”

React.js this.props.data.map() is not a function

Nessuno di questi mi ha aiutato a risolvere il problema. Dopo il caricamento della pagina, posso verificare che questo.data.props non sia indefinito (e abbia un valore equivalente all'oggetto json - può chiamare con window.foo), quindi sembra che non si carichi in tempo quando viene chiamato per ConversationList. Come faccio a essere sicuro che il metodo map funzioni sui dati JSON e non su una variabile undefined?

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 campione conversations.json

Nota - chiamando this.props.data.conversations restituisce anche un errore:

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

rendimenti

Uncaught TypeError: Cannot read property 'map' of undefined

Ecco conversations.json:

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

+0

può voi pubblica un esempio di conversations.json? – user120242

+0

Aggiunto. Molte grazie! – keypulsations

+0

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

risposta

61

La funzione .map è disponibile solo su array.
Sembra che data non sia nel formato che ci si aspetta che sia (è {} ma ci si aspetta []).

this.setState({data: data}); 

dovrebbe essere

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

Verifica a quanto "dati" Type è stato impostato a, e assicurarsi che si tratta di 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') 
    ); 
    } 
}) 
+1

Bingo! Questo ha fatto il trucco, grazie mille :) – keypulsations

5

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

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

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

+0

Questo è un bel consiglio/trucco, ma voglio sottolineare che questo maschererebbe il vero problema e non risolverebbe OP. Nel caso dell'OP questo non sarebbe d'aiuto, dato che i dati sarebbero ancora malformati (non ciò che era inteso). La conversazione sarebbe probabilmente vuota perché tutti gli oggetti di scena sarebbero nulli. Raccomanderei di non utilizzare questo suggerimento, a meno che non si sia certi che i dati siano nel formato desiderato, quindi non si finisce con un comportamento imprevisto quando i dati restituiti non sono quelli che si pensava dovesse essere. – user120242

1

Non è necessario un array per farlo.

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

Com'è diverso fratello? –

0

Succede perché il componente viene eseguito prima che i dati asincroni siano arrivati, è necessario controllare prima di eseguire il rendering.

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> 
    ); 
} 
  • Mappa non può risolvere quando la proprietà è null/indefinito, così ho fatto un controllo prima

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

Problemi correlati