Дочерний компонент React не обновляется после изменения родительского состояния


111

Я пытаюсь создать хороший компонент ApiWrapper для заполнения данных в различных дочерних компонентах. Из всего, что я прочитал, это должно сработать: 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')
);

Но по какой-то причине кажется, что дочерний компонент не обновляется при изменении родительского состояния.

Я что-то упустил?

Ответы:


207

В вашем коде есть две проблемы.

Начальное состояние вашего дочернего компонента устанавливается из props.

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

Цитата из этого ответа SO :

Передача начального состояния компоненту в качестве a propявляется анти-шаблоном, потому что метод getInitialState(в нашем случае констуктор) вызывается только при первом отрисовке компонента. Никогда больше. Это означает, что если вы повторно визуализируете этот компонент, передав другое значение как a prop, компонент не будет реагировать соответствующим образом, потому что компонент сохранит состояние с первого раза, когда он был визуализирован. Это очень подвержено ошибкам.

Так что, если вы не можете избежать такой ситуации, идеальным решением будет использовать этот метод componentWillReceivePropsдля прослушивания новых свойств.

Добавление приведенного ниже кода к вашему дочернему компоненту решит вашу проблему с повторным рендерингом дочернего компонента.

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

Вторая проблема связана с расширением fetch.

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

А вот и рабочая скрипка: https://jsfiddle.net/o8b04mLy/


1
«Это нормально инициализировать состояние на основе props, если вы знаете, что делаете». Есть ли другие недостатки в настройке состояния через prop, кроме того факта, что без него не nextPropзапускается повторный рендеринг componentWillReceiveProps(nextProps)?
Винни Джеймс

11
Насколько я знаю, других минусов нет. Но в вашем случае мы могли бы явно избежать состояния внутри дочернего компонента. Родитель может просто передавать данные в качестве реквизита, и когда родительский объект выполняет повторную рендеринг с новым состоянием, дочерний элемент также будет повторно рендерить (с новыми реквизитами). Собственно поддержание состояния внутри ребенка здесь не нужно. Чистые компоненты FTW!
Ядху Киран

7
Для тех, кто читает с этого static getDerivedStateFromProps(nextProps, prevState) момента
GoatsWearHats

4
Есть ли какое-либо другое решение для этого, кроме componentWillReceiveProps (), потому что теперь оно устарело?
LearningMath

3
@LearningMath, пожалуйста, посмотрите последнюю документацию по React, в которой рассказывается об альтернативных способах. Возможно, вам придется пересмотреть свою логику.
Ядху Киран

1

Есть кое-что, что вам нужно изменить.

Когда fetchполучите ответ, это не json. Я искал, как мне получить этот json, и обнаружил эту ссылку .

С другой стороны, вы должны думать, что constructorфункция вызывается только один раз.

Итак, вам нужно изменить способ получения данных в <Child>компоненте.

Здесь я оставил пример кода: https://jsfiddle.net/emq1ztqj/

Надеюсь, это поможет.


Спасибо. Хотя, глядя на созданный вами пример, кажется, что Child никогда не обновляется. Любые предложения о том, как изменить способ получения данных Child?
Винни Джеймс

2
Ты уверен? Я вижу, что этот <Child>компонент извлекает новые данные https://jsonplaceholder.typicode.com/posts/1и выполняет их повторную визуализацию.
slorenzo

1
Да, теперь я вижу, что это работает на OSX. iOS не запускала повторный рендеринг в браузере приложения StackExchange
Винни Джеймс,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.