Подтвердить что ты не робот

React - setState() для размонтированного компонента

В моем реагирующем компоненте я пытаюсь реализовать простой счетчик, пока выполняется запрос ajax - im использует состояние для сохранения состояния загрузки.

По какой-то причине этот фрагмент кода в моем компоненте React вызывает эту ошибку

Может обновлять только смонтированный или монтажный компонент. Обычно это означает вы вызываете setState() на несмонтированном компоненте. Это не-op. Проверьте код для компонента undefined.

Если я избавлюсь от первого вызова setState, ошибка исчезнет.

constructor(props) {
  super(props);
  this.loadSearches = this.loadSearches.bind(this);

  this.state = {
    loading: false
  }
}

loadSearches() {

  this.setState({
    loading: true,
    searches: []
  });

  console.log('Loading Searches..');

  $.ajax({
    url: this.props.source + '?projectId=' + this.props.projectId,
    dataType: 'json',
    crossDomain: true,
    success: function(data) {
      this.setState({
        loading: false
      });
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
      this.setState({
        loading: false
      });
    }.bind(this)
  });
}

componentDidMount() {
  setInterval(this.loadSearches, this.props.pollInterval);
}

render() {

    let searches = this.state.searches || [];


    return (<div>
          <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Submit Date</th>
              <th>Dataset &amp; Datatype</th>
              <th>Results</th>
              <th>Last Downloaded</th>
            </tr>
          </thead>
          {
          searches.map(function(search) {

                let createdDate = moment(search.createdDate, 'X').format("YYYY-MM-DD");
                let downloadedDate = moment(search.downloadedDate, 'X').format("YYYY-MM-DD");
                let records = 0;
                let status = search.status ? search.status.toLowerCase() : ''

                return (
                <tbody key={search.id}>
                  <tr>
                    <td>{search.name}</td>
                    <td>{createdDate}</td>
                    <td>{search.dataset}</td>
                    <td>{records}</td>
                    <td>{downloadedDate}</td>
                  </tr>
                </tbody>
              );
          }
          </Table >
          </div>
      );
  }
4b9b3361

Ответ 1

Не видя, что функция рендеринга немного жесткая. Хотя вы уже можете заметить что-то, что вы должны делать, каждый раз, когда вы используете интервал, который вы получили, чтобы очистить его при размонтировании. Итак:

componentDidMount() {
    this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}

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

this.loadInterval && this.setState({
    loading: false
});

Надеемся, что это поможет, предоставит функцию рендеринга, если это не делает работу.

Приветствия

Ответ 2

Вопрос в том, почему я получаю эту ошибку, когда компонент уже должен быть установлен (поскольку он вызывается из componentDidMount), я думал, что безопасно устанавливать состояние после монтирования компонента?

Он не вызывается из componentDidMount. Ваш componentDidMount создает функцию обратного вызова, которая будет выполняться в стеке обработчика таймера, а не в стеке componentDidMount. По-видимому, к моменту, когда ваш обратный вызов (this.loadSearches) будет выполнен, компонент отключился.

Таким образом, принятый ответ защитит вас. Если вы используете какой-либо другой асинхронный API, который не позволяет отменить асинхронные функции (уже переданные некоторым обработчикам), вы можете сделать следующее:

if (this.isMounted())
     this.setState(...

Это избавит вас от сообщения об ошибке, которое вы сообщаете во всех случаях, хотя оно действительно похоже на подметание под ковриком, особенно если ваш API предоставляет возможность отмены (как setInterval делает с clearInterval).

Ответ 3

Кому нужна другая опция, метод обратного вызова атрибута ref может быть обходным путем. Параметр handleRef является ссылкой на элемент DOM div.

Для получения подробной информации о ссылках и DOM: https://facebook.github.io/react/docs/refs-and-the-dom.html

handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}

Ответ 4

class myClass extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;
    this._getData();
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  _getData() {
    axios.get('https://example.com')
      .then(data => {
        if (this._isMounted) {
          this.setState({ data })
        }
      });
  }


  render() {
    ...
  }
}

Ответ 5

Для потомков

Эта ошибка в нашем случае была связана с Reflux, обратными вызовами, перенаправлениями и setState. Мы отправили setState на обратный вызов onDone, но мы также отправили перенаправление на обратный вызов onSuccess. В случае успеха наш обратный вызов onSuccess выполняется до onDone. Этот вызывает перенаправление перед попыткой setState. Таким образом, ошибка, setState на немонтированном компоненте.

Действие хранилища рефлекса:

generateWorkflow: function(
    workflowTemplate,
    trackingNumber,
    done,
    onSuccess,
    onFail)
{...

Вызов перед исправлением:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    this.setLoading.bind(this, false),
    this.successRedirect
);

Вызов после исправления:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    null,
    this.successRedirect,
    this.setLoading.bind(this, false)
);

Подробнее

В некоторых случаях, поскольку React isMounted является "устаревшим/анти-шаблоном", мы приняли использование переменной _mounted и сами ее контролируем.

Ответ 6

Поделитесь решением, включенным реагирующими хуками.

React.useEffect(() => {
  let isSubscribed = true

  callApi(...)
    .catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
    .then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
    .catch(({ isSubscribed, ...err }) => console.error('request cancelled:', !isSubscribed))

  return () => (isSubscribed = false)
}, [])

это же решение может быть расширено, если вы хотите отменить предыдущие запросы на изменение идентификатора выборки, в противном случае могут быть условия гонки среди нескольких запросов в полете (this.setState не по порядку).

React.useEffect(() => {
  let isCancelled = false

  callApi(id).then(...).catch(...) // similar to above

  return () => (isCancelled = true)
}, [id])

это работает благодаря замыканиям в JavaScript.

В целом, идея, приведенная выше, была близка к подходу makeCancelable, рекомендованному реагирующим документом, в котором четко говорится

isMounting - это Антипаттерн

кредит

https://juliangaramendy.dev/use-promise-subscription/