Реагировать - setState () на несмонтированный компонент

73

В моем реагирующем компоненте я пытаюсь реализовать простой счетчик, пока выполняется запрос 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>
      );
  }

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

  • 0
    в моем конструкторе я устанавливаю "this.loadSearches = this.loadSearches.bind (this);" - добавлю это к вопросу
  • 0
    Вы пытались установить загрузку в нуль в своем конструкторе? Это может сработать. this.state = { loading : null };
Теги:
state

5 ответов

57
Лучший ответ

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

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
    Бруно, не могли бы вы просто проверить наличие «этого» контекста .. аля это && this.setState .....
  • 6
    Или просто: componentWillUnmount() { clearInterval(this.loadInterval); }
Показать ещё 2 комментария
12

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

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

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

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

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

  • 12
    isMounted - это антипаттерн, который Facebook рекомендует не использовать: facebook.github.io/react/blog/2015/12/16/…
  • 1
    Да, я говорю, что "это похоже на то, как будто подметать под ковром".
5

Кому нужна другая опция, метод обратного вызова атрибута 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>
 )
}
  • 5
    Использование ref для эффективного isMounting - это то же самое, что и просто использование isMounting, но менее понятное. isMounting не является анти-шаблоном из-за его имени, а потому, что это анти-шаблон для хранения ссылок на размонтированный компонент.
1

Для потомков

Эта ошибка в нашем случае была связана с 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 и сами ее контролируем.

0
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() {
    ...
  }
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню