Le composant enfant React ne se met pas à jour après le changement d'état parent

109

J'essaie de créer un joli composant ApiWrapper pour remplir les données dans divers composants enfants. D'après tout ce que j'ai lu, cela devrait fonctionner: 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')
);

Mais pour une raison quelconque, il semble que le composant enfant ne se met pas à jour lorsque l'état parent change.

Est-ce que j'ai râté quelque chose?

Vinnie James
la source

Réponses:

205

Il y a deux problèmes avec votre code.

L'état initial de votre composant enfant est défini à partir des accessoires.

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

Citant cette réponse SO :

Passer l'état initial à un composant en tant que propest un anti-motif car la getInitialStateméthode (dans notre cas, le constuctor) n'est appelée que la première fois que le composant est rendu. Jamais plus. Cela signifie que si vous restaurez ce composant en passant une valeur différente en tant que a prop, le composant ne réagira pas en conséquence, car le composant conservera l'état de la première fois qu'il a été rendu. C'est très sujet aux erreurs.

Donc, si vous ne pouvez pas éviter une telle situation, la solution idéale est d'utiliser la méthode componentWillReceivePropspour écouter de nouveaux accessoires.

L'ajout du code ci-dessous à votre composant enfant résoudra votre problème de re-rendu du composant enfant.

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

Le deuxième problème concerne le fetch.

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

Et voici un violon fonctionnel: https://jsfiddle.net/o8b04mLy/

Yadhu Kiran
la source
1
"Il est normal d' initialiser l'état en fonction des accessoires si vous savez ce que vous faites" Y a-t-il d'autres inconvénients à définir l'état via prop, à part le fait que nextPropne déclenchera pas un nouveau rendu sans componentWillReceiveProps(nextProps)?
Vinnie James
11
Autant que je sache, il n'y a pas d'autres inconvénients. Mais dans votre cas, nous pourrions clairement éviter d'avoir un état à l'intérieur du composant enfant. Le parent peut simplement passer des données en tant qu'accessoires, et lorsque le parent effectue un nouveau rendu avec son nouvel état, l'enfant effectue également un nouveau rendu (avec de nouveaux accessoires). En fait, maintenir un état à l'intérieur de l'enfant n'est pas nécessaire ici. Composants purs FTW!
Yadhu Kiran
7
Pour tous ceux qui liront à partir de maintenant, consultez static getDerivedStateFromProps(nextProps, prevState) reactjs.org/docs/…
GoatsWearHats
4
Existe-t-il une autre solution à cela que componentWillReceiveProps () car il est maintenant obsolète?
LearningMath
3
@LearningMath, veuillez consulter les derniers documents React qui parlent de méthodes alternatives. Vous devrez peut-être reconsidérer votre logique.
Yadhu Kiran
1

Il y a certaines choses que vous devez changer.

Lorsque fetchvous obtenez la réponse, ce n'est pas un json. Je cherchais comment puis-je obtenir ce json et j'ai découvert ce lien .

De l'autre côté, vous devez penser que la constructorfonction n'est appelée qu'une seule fois.

Vous devez donc changer la façon dont vous récupérez les données dans le <Child>composant.

Ici, j'ai laissé un exemple de code: https://jsfiddle.net/emq1ztqj/

J'espère que cela aide.

Slorenzo
la source
Je vous remercie. Bien que, en regardant l'exemple que vous avez fait, il semble toujours que Child ne se mette jamais à jour. Des suggestions sur la façon de changer la façon dont Child reçoit les données?
Vinnie James
2
Êtes-vous sûr? Je vois que ce <Child>composant récupère les nouvelles données https://jsonplaceholder.typicode.com/posts/1et effectue un nouveau rendu.
slorenzo
1
Oui, je le vois fonctionner maintenant sur OSX. iOS n'a pas déclenché le nouveau rendu dans le navigateur de l'application StackExchange
Vinnie James