Reaguj Komponent podrzędny nie aktualizuje się po zmianie stanu nadrzędnego

109

Próbuję stworzyć ładny składnik ApiWrapper do wypełniania danych w różnych składnikach podrzędnych. Ze wszystkiego, co przeczytałem, powinno to działać: 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')
);

Ale z jakiegoś powodu wydaje się, że komponent potomny nie aktualizuje się, gdy zmienia się stan nadrzędny.

Czy coś mi umyka?

Vinnie James
źródło

Odpowiedzi:

205

Twój kod ma dwa problemy.

Początkowy stan twojego komponentu podrzędnego jest ustawiany na podstawie właściwości.

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

Cytując z tej odpowiedzi SO :

Przekazanie stanu początkowego do komponentu jako a propjest anty-wzorcem, ponieważ getInitialStatemetoda (w naszym przypadku konstruktor) jest wywoływana tylko przy pierwszym renderowaniu komponentu. Nigdy więcej. Oznacza to, że jeśli ponownie wyrenderujesz ten komponent, przekazując inną wartość jako a prop, komponent nie zareaguje odpowiednio, ponieważ zachowa stan z pierwszego renderowania. Jest to bardzo podatne na błędy.

Jeśli więc nie możesz uniknąć takiej sytuacji, idealnym rozwiązaniem jest zastosowanie tej metody componentWillReceiveProps nasłuchiwania nowych rekwizytów.

Dodanie poniższego kodu do komponentu podrzędnego rozwiąże problem z ponownym renderowaniem komponentu podrzędnego.

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

Drugi problem dotyczy fetch.

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

A oto działające skrzypce: https://jsfiddle.net/o8b04mLy/

Yadhu Kiran
źródło
1
„Można zainicjować stan na podstawie właściwości, jeśli wiesz, co robisz” Czy są jakieś inne wady ustawiania stanu za pomocą właściwości, poza tym, że bez tego nextPropnie spowoduje to ponownego renderowania componentWillReceiveProps(nextProps)?
Vinnie James,
11
O ile wiem, nie ma innych wad. Ale w twoim przypadku moglibyśmy wyraźnie uniknąć posiadania stanu wewnątrz komponentu potomnego. Rodzic może po prostu przekazać dane jako właściwości, a gdy rodzic renderuje się ponownie ze swoim nowym stanem, dziecko również renderuje ponownie (z nowymi właściwościami). Właściwie utrzymywanie stanu wewnątrz dziecka jest tutaj niepotrzebne. Czyste komponenty FTW!
Yadhu Kiran
7
Każdy, kto czyta od tej pory, może sprawdzić static getDerivedStateFromProps(nextProps, prevState) reactjs.org/docs/ ...
GoatsWearHats
4
Czy istnieje inne rozwiązanie tego problemu niż componentWillReceiveProps (), ponieważ jest teraz przestarzałe?
LearningMath
3
@LearningMath, zapoznaj się z najnowszymi dokumentami React, które mówią o alternatywnych sposobach. Być może będziesz musiał ponownie przemyśleć swoją logikę.
Yadhu Kiran
1

Jest kilka rzeczy, które musisz zmienić.

Po fetchotrzymaniu odpowiedzi nie jest to plik json. Szukałem, jak mogę uzyskać ten plik json i odkryłem ten link .

Z drugiej strony, musisz pomyśleć, że constructorfunkcja jest wywoływana tylko raz.

Dlatego musisz zmienić sposób pobierania danych w <Child>komponencie.

Tutaj zostawiłem przykładowy kod: https://jsfiddle.net/emq1ztqj/

Mam nadzieję że to pomogło.

slorenzo
źródło
Dziękuję Ci. Chociaż patrząc na przykład, który stworzyłeś, nadal wydaje się, że Child nigdy nie aktualizuje. Jakieś sugestie, jak zmienić sposób, w jaki Dziecko otrzymuje dane?
Vinnie James,
2
Jesteś pewny? Widzę, że <Child>komponent pobiera nowe dane https://jsonplaceholder.typicode.com/posts/1i ponownie renderuje.
slorenzo
1
Tak, widzę, że teraz działa na OSX. iOS nie uruchomił ponownego renderowania w przeglądarce aplikacji StackExchange
Vinnie James,