2017-09-27 1 views
3

Ich habe eine Redux-Komponente, die JSON (im unteren Teil) analysiert, aber ich kann nicht herausfinden, wie die verschachtelten untergeordneten Objekte greifen. Ich glaube nicht, dass ich richtig verstehe, wie mapStateToProps funktioniert.React/Redux mapStateToProps mit verschachtelten JSON

Das Konsolenprotokoll Dumping die untergeordneten Objekte, aber wenn ich versuche services.name ich zugreifen

"Cannot read property 'name' of undefined"

Kann mir jemand helfen zu verstehen, wie Eigenschaften hier abzubilden? Ich habe ein Beispiel für den JSON aufgenommen, den ich unten aus der API ziehe.

Dienste-list.js

import React, { Component } from 'react'; 
import { connect } from 'react-redux'; 
import * as actions from '../actions'; 

class ServicesList extends Component { 

    componentDidMount(){ 
    this.props.fetchServices(); 
    } 

    render() { 
    //console.log('render called in ServicesList component'); 
    return (
     <table className='table table-hover'> 
     <thead> 
      <tr> 
      <th>Service Name</th> 
      </tr> 
     </thead> 
     <tbody> 
      {this.props.services.map(this.renderServices)} 
     </tbody> 
     </table> 
    ); 
    } 

    renderServices(data) { 
    console.log(data.services); 
    const name = data.services.name; 
    return(
     <tr key={name}> 
     <td>{name}</td> 
     </tr> 
    ); 
    } 
} 

function mapStateToProps({services}) { 
    return { services }; 
} 

export default connect(mapStateToProps, actions)(ServicesList); 

Meine JSON wie folgt aussieht:

{ 
    "services": [ 
    { 
     "name": "redemption-service", 
     "versions": { 
      "desired": "20170922_145033", 
      "deployed": "20170922_145033" 
     }, 
     "version": "20170922_145033" 
    }, { 
     "name": "client-service", 
     "versions": { 
      "desired": "20170608_094954", 
      "deployed": "20170608_094954" 
     }, 
     "version": "20170608_094954" 
    }, { 
     "name": "content-rules-service", 
     "versions": { 
      "desired": "20170922_130454", 
      "deployed": "20170922_130454" 
     }, 
     "version": "20170922_130454" 
    } 
    ] 
} 

Schließlich habe ich eine Aktion, die die axios.get hier aussetzt:

import axios from 'axios'; 

const ROOT_URL=`http://localhost:8080/services.json`; 

export const FETCH_SERVICES = 'FETCH_SERVICES'; 

export function fetchServices(){ 
    const url = `${ROOT_URL}`; 
    const request = axios.get(url); 

    return{ 
    type: FETCH_SERVICES, 
    payload: request 
    }; 
} 
+0

Ihre 'const name = data.services.name;' 'sollte const name = data.name sein;' – Panther

+0

Das bcoz ist, Ihre kartieren über 'services' und für jeden loop erhalten Sie einen einzigen 'service' in der' renderServices' Funktion. – Panther

+0

Richtig, es ruft renderServices genau einmal auf. Was ich nicht verstehe, ist, wie man das nächste Objekt abbildet, wenn es nicht benannt ist. –

Antwort

2

Ich nehme an, dass Sie denken this.props.fetchServices() wird die services Reducer aktualisieren und dann wird die 01 übergebenals eine Stütze über die mapStateToProps.
Wenn dies korrekt ist, beachten Sie, dass Sie innerhalb componentWillMount holen und dies ist ein BIG nein nein.
Zitat aus dem componentWillMount DOCS:

Avoid introducing any side-effects or subscriptions in this method.

Sie sollten Daten in componentDidMount holen.

Außerdem denken Sie wahrscheinlich, dass die Rendermethode nicht aufgerufen wird, bis Sie Ihre Daten von der Ajax-Anfrage zurückbekommen haben. Sie sehen, reagieren nicht auf Ihren Ajax-Anruf warten, um mit den Daten zurück zu kommen, die render Methode wird aufgerufen, egal was, so der erste render Aufruf wird versuchen, map auf einem leeren Array von services (Sie haben ein leeres Array als Anfangszustand in deinem Reduzierer nehme ich an).
Dann Ihre renderServices Funktion wird ein leeres Array als data bekommen und data.services ist in der Tat undefined daher, wenn Sie versuchen data.services.name Sie den Fehler zuzugreifen:

"Cannot read property 'name' of undefined"

Nur eine Bedingung verwenden, um Ihre in Render:

<tbody> 
    {this.props.services && this.props.services.map(this.renderServices)} 
</tbody> 

Bearbeiten
Als Follow-up zu Ihrem Kommentar versuchen Sie, auf eine object zu mappen aber .map funktioniert auf Arrays. so eigentlich sollten Sie auf services.services.map(...) anstelle von services.map abbilden, obwohl Sie noch überprüfen müssen, ob es existiert.
Ich habe ein funktionierendes Beispiel mit Ihrem Code gemacht, ich habe keine redux und ajax Anfragen, aber ich verwendete die gleichen Daten, die Sie verwenden, und ich gebe es nur auf dem zweiten Render von ServicesList, so hat es im Grunde das gleiche Szenario, dem Sie gegenüber stehen.
Ich habe sogar ein Timeout hinzugefügt, um eine Verzögerung zu imitieren + hinzugefügt eine Ladeanzeige, um zu demonstrieren, was Sie mit bedingten Rendering tun können (oder sollten).

const fakeData = { 
 
    services: [ 
 
    { 
 
     name: "redemption-service", 
 
     versions: { 
 
     desired: "20170922_145033", 
 
     deployed: "20170922_145033" 
 
     }, 
 
     version: "20170922_145033" 
 
    }, 
 
    { 
 
     name: "client-service", 
 
     versions: { 
 
     desired: "20170608_094954", 
 
     deployed: "20170608_094954" 
 
     }, 
 
     version: "20170608_094954" 
 
    }, 
 
    { 
 
     name: "content-rules-service", 
 
     versions: { 
 
     desired: "20170922_130454", 
 
     deployed: "20170922_130454" 
 
     }, 
 
     version: "20170922_130454" 
 
    } 
 
    ] 
 
}; 
 

 
class ServicesList extends React.Component { 
 
    componentDidMount() { 
 
    this.props.fetchServices(); 
 
    } 
 

 
    render() { 
 
    const { services } = this.props; 
 

 
    return (
 
     <table className="table table-hover"> 
 
     <thead> 
 
      <tr> 
 
      <th>Service Name</th> 
 
      </tr> 
 
     </thead> 
 
     <tbody> 
 
      {services.services ? (
 
      services.services.map(this.renderServices) 
 
     ) : (
 
      this.renderLoader() 
 
     )} 
 
     </tbody> 
 
     </table> 
 
    ); 
 
    } 
 

 
    renderLoader() { 
 
    return (
 
     <tr> 
 
     <td>Loading...</td> 
 
     </tr> 
 
    ); 
 
    } 
 

 
    renderServices(data) { 
 
    const name = data.name; 
 
    return (
 
     <tr key={name}> 
 
     <td>{name}</td> 
 
     </tr> 
 
    ); 
 
    } 
 
} 
 

 
class App extends React.Component { 
 
    constructor(props) { 
 
    super(props); 
 
    this.state = { 
 
     data: {} 
 
    }; 
 
    this.fetchServices = this.fetchServices.bind(this); 
 
    } 
 

 
    fetchServices() { 
 
    setTimeout(() => { 
 
     this.setState({ data: { ...fakeData } }); 
 
    }, 1500); 
 
    } 
 

 
    render() { 
 
    const { data } = this.state; 
 
    return (
 
     <div> 
 
     <ServicesList services={data} fetchServices={this.fetchServices} /> 
 
     </div> 
 
    ); 
 
    } 
 
} 
 

 
ReactDOM.render(<App />, document.getElementById("root"));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> 
 
<div id="root"></div>

+0

Okay, ich habe aktualisiert, um den Abruf in einem ComponentDidMount stattdessen zu senden, aber das gleiche Problem bleibt ... die Daten kommen zurück (Ich kann die gesamten 30 Datensätze protokolliert werden), aber ich weiß einfach nicht, wie die JSON zuordnen hat einen einzigen Datensatz der obersten Ebene. Die Frage war, wie überspringe ich den Top-Rekord und kartiere die Kinder. –

+0

das 'componentDidMount' war nicht die Lösung, ich habe es gerade erwähnt, weil es eine schlechte Übung ist, es mit' componentWillMount' zu machen. Ich habe meine Antwort mit mehr Erklärungen und einem Arbeitsbeispiel aktualisiert, ich denke, das sollte dich auf den richtigen Weg bringen. :) –

+0

Super! Danke für die Hilfe. Das hat funktioniert. –

Verwandte Themen