Il componente figlio React non si aggiorna dopo la modifica dello stato del genitore


109

Sto tentando di creare un bel componente ApiWrapper per popolare i dati in vari componenti figlio. Da tutto quello che ho letto, dovrebbe funzionare: https://jsfiddle.net/vinniejames/m1mesp6z/1/

class ApiWrapper extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      response: {
        "title": 'nothing fetched yet'
      }
    };
  }

  componentDidMount() {
    this._makeApiCall(this.props.endpoint);
  }

  _makeApiCall(endpoint) {
    fetch(endpoint).then(function(response) {
      this.setState({
        response: response
      });
    }.bind(this))
  }

  render() {
    return <Child data = {
      this.state.response
    }
    />;
  }
}

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

  render() {
    console.log(this.state.data, 'new data');
    return ( < span > {
      this.state.data.title
    } < /span>);
  };
}

var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;

ReactDOM.render(
  element,
  document.getElementById('container')
);

Ma per qualche ragione, sembra che il componente figlio non si aggiorni quando lo stato genitore cambia.

Mi manca qualcosa qui?

Risposte:


205

Ci sono due problemi con il tuo codice.

Lo stato iniziale del tuo componente figlio è impostato da props.

this.state = {
  data: props.data
};

Citando da questa risposta SO :

Passare lo stato iniziale a un componente come a propè un anti-pattern perché il getInitialStatemetodo (nel nostro caso il constuctor) viene chiamato solo la prima volta che il componente esegue il rendering. Mai più. Ciò significa che, se si esegue nuovamente il rendering di quel componente passando un valore diverso come a prop, il componente non reagirà di conseguenza, perché il componente manterrà lo stato dalla prima volta che è stato renderizzato. È molto soggetto a errori.

Quindi, se non puoi evitare una situazione del genere, la soluzione ideale è utilizzare il metodo componentWillReceivePropsper ascoltare nuovi oggetti di scena.

L'aggiunta del codice seguente al componente figlio risolverà il problema con il rendering del componente figlio.

componentWillReceiveProps(nextProps) {
  this.setState({ data: nextProps.data });  
}

Il secondo problema riguarda il fetch.

_makeApiCall(endpoint) {
  fetch(endpoint)
    .then((response) => response.json())   // ----> you missed this part
    .then((response) => this.setState({ response }));
}

Ed ecco un violino funzionante: https://jsfiddle.net/o8b04mLy/


1
"Va bene inizializzare lo stato in base agli oggetti di scena se sai cosa stai facendo" Ci sono altri svantaggi nell'impostazione dello stato tramite prop, oltre al fatto che nextPropnon si attiverà un nuovo rendering senza componentWillReceiveProps(nextProps)?
Vinnie James,

11
Per quanto ne so, non ci sono altri svantaggi. Ma nel tuo caso potremmo chiaramente evitare di avere uno stato all'interno del componente figlio. Il genitore può semplicemente passare i dati come oggetti di scena, e quando il genitore esegue nuovamente il rendering con il suo nuovo stato, anche il figlio esegue nuovamente il rendering (con nuovi oggetti di scena). In realtà qui non è necessario mantenere uno stato all'interno del bambino. Componenti puri FTW!
Yadhu Kiran

7
Per chiunque legga d'ora in poi, controlla static getDerivedStateFromProps(nextProps, prevState) reactjs.org/docs/…
GoatsWearHats

4
C'è qualche altra soluzione a questo diverso da componentWillReceiveProps () perché è deprecato ora?
LearningMath

3
@LearningMath per favore guarda gli ultimi documenti di React che parlano di modi alternativi. Potresti dover riconsiderare la tua logica.
Yadhu Kiran,

1

Ci sono alcune cose che devi cambiare.

Quando fetchottieni la risposta, non è un json. Stavo cercando come posso ottenere questo json e ho scoperto questo collegamento .

D'altro canto, devi pensare che la constructorfunzione venga chiamata solo una volta.

Quindi, è necessario modificare il modo in cui si recuperano i dati in <Child>component.

Qui, ho lasciato un codice di esempio: https://jsfiddle.net/emq1ztqj/

Spero che aiuti.


Grazie. Anche se, guardando l'esempio che hai fatto, sembra ancora che Child non si aggiorni mai. Qualche suggerimento su come cambiare il modo in cui il bambino riceve i dati?
Vinnie James,

2
Sei sicuro? Vedo che il <Child>componente recupera i nuovi dati da https://jsonplaceholder.typicode.com/posts/1ed esegue nuovamente il rendering.
slorenzo

1
Sì, lo vedo ora funzionare su OSX. iOS non attivava il re-rendering nel browser dell'app StackExchange
Vinnie James,
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.