Документ не определяется при попытке установитьState из возврата асинхронного вызова в компонентеWillMount

Я получаю свои данные в своем вызове componentWillMount моего компонента [на самом деле это в mixin, но в той же идее]. После возврата вызова ajax я пытаюсь установитьState, но я получаю ошибку, что документ не определен.

Я не уверен, как обойти это. Есть что ждать? Обещание или обратный вызов я должен делать setState в?

Это то, что я пытаюсь сделать:

componentWillMount: function() {
    request.get(this.fullUrl()).end(function(err, res) {
        this.setState({data: res.body});
    }.bind(this));
}

Ответ 1

Я уже сталкивался с подобной ситуацией раньше. Я предполагаю, что ошибка, с которой вы столкнулись, была примерно такой:

Uncaught Error: Invariant Violation: replaceState(...): Can only update a mounted or mounting component.

Ошибка вызвана тем, что в компонентах React вы не можете установить состояние до того, как компонент будет установлен. Итак, вместо того, чтобы пытаться установить состояние в componentWillMount, вы должны сделать это в componentDidMount. Обычно я добавляю проверку .isMounted(), только для хорошей меры.

Попробуйте что-то вроде этого:

componentDidMount: function () {
  request.get(this.fullUrl()).end(function(err, res) {
    if (this.isMounted()) {
      this.setState({data: res.body});
    }
  }.bind(this));
}


РЕДАКТИРОВАТЬ: Забыл упомянуть... Если компонент "размонтирован" перед завершением операции асинхронизации, вы также можете столкнуться с ошибкой.

Это можно легко обработать, если операция async "отменена". Предполагая, что ваш request() выше - это что-то вроде запроса superagent (который отменяется), я бы сделал следующее, чтобы избежать возможных ошибок.

componentDidMount: function () {
  this.req = request.get(this.fullUrl()).end(function(err, res) {
    if (this.isMounted()) {
      this.setState({data: res.body});
    }
  }.bind(this));
},

componentWillUnmount: function () {
  this.req.abort();
}


РЕДАКТИРОВАТЬ № 2: В одном из наших комментариев вы упомянули о своем намерении создать изоморфное решение, которое может загружать состояние асинхронно. Хотя это выходит за рамки первоначального вопроса, я предлагаю вам проверить react-async. Из-за коробки он предоставляет 3 инструмента, которые помогут вам достичь этой цели.
  • getInitialStateAsync - это обеспечивается через mixin и позволяет компоненту извлекать данные состояния асинхронно.

    var React = require('react')
    var ReactAsync = require('react-async')
    
    var AwesomeComponent = React.createClass({
      mixins: [ReactAsync.Mixin],
    
      getInitialStateAsync: function(callback) {
        doAsyncStuff('/path/to/async/data', function(data) {
          callback(null, data)
        }.bind(this))
      },
    
      render: function() {
         ... 
      }
    });
    
  • renderToStringAsync() - который позволяет отображать серверную сторону

    ReactAsync.renderToStringAsync(
      <AwesomeComponent />,
      function(err, markup, data) {
        res.send(markup);
      })
    );
    
  • injectIntoMarkup() - который будет вводить состояние сервера вместе с разметкой для обеспечения доступности клиентской стороны

    ReactAsync.renderToStringAsync(
      <AwesomeComponent />,
      function(err, markup, data) {
        res.send(ReactAsync.injectIntoMarkup(markup, data, ['./app.js']));
      })
    );
    

react-async обеспечивает гораздо больше функциональности, чем это. Вы должны проверить документацию по реагированию-асинхронизации для получения полного списка его функций и более полное объяснение тех, которые я кратко описал выше.

Ответ 2

Не рекомендуется делать что-то асинхронное внутри componentWillMount. Вы действительно должны делать это в componentDidMount, потому что, если первая задача, которую выполняет компонент, - это получить некоторые данные - вы, вероятно, захотите, чтобы она показывала счетчик или какой-либо опознаватель загрузки, прежде чем он получит эти данные.

В результате я лично никогда не делаю то, что вы делаете, каждый раз выбирая componentDidMount. Затем вы можете установить свое начальное состояние, чтобы этот первый монтажный рендеринт показывал пользователю загрузчик или какой-либо другой начальный статус. Ajax срабатывает, и вы обновляетесь после получения ответа. Таким образом, вы знаете, что вы обрабатываете случаи, когда ваш пользователь находится на дрянной связи, такой как мобильный с плохим приемом или таким образом, предоставляя хороший UX, позволяя пользователю узнать, что компонент загружает некоторые данные, Пока ничего не вижу.

Что все сказано, почему вы получаете ошибку при выполнении некоторых асинхронных функций внутри componentWillMount - потому что, если вы только что вызвали this.setState внутри самой функции жизненного цикла, это сработает нормально? Это зависит от того, как работает React, он был вокруг, по крайней мере, с React 0.11, насколько мне известно. Когда вы монтируете компонент, выполнение this.setState синхронно внутри componentWillMount работает просто отлично (хотя есть ошибка в 0.12.x, где любой обратный вызов функции, переданный в setState внутри componentWillMount, не будет выполнен). Это связано с тем, что React понимает, что вы устанавливаете состояние на компоненте, который еще не установлен - что-то, что вы обычно не можете делать, но оно позволяет использовать его в функциях жизненного цикла, таких как componentWillMount. Однако, когда вы асинхронизируете вызов setState, он больше не обрабатывается специально и применяются обычные правила - вы не можете setState на компоненте, который не установлен. Если ваш запрос ajax возвращается очень быстро, вполне возможно, что ваш вызов setState происходит ПОСЛЕ фазы componentWillMount, но ПЕРЕД компонентом на самом деле смонтирован - и вы получите сообщение об ошибке. Если на самом деле ваш запрос ajax был не таким быстрым, как это очевидно, скажем, это заняло секунду или больше, тогда вы, вероятно, не заметили бы ошибку, так как очень вероятно, что ваш компонент смонтирован полностью за секунду, и поэтому ваш setState вызов снова становится действительным с помощью обычных правил. Но вы в основном даете себе условия гонки здесь, будьте в безопасности и используйте componentDidMount вместо этого, поскольку это также лучше по другим причинам, о которых я говорил выше.

Некоторые люди говорят, что вы можете сделать это внутри setTimeout, и они верны, но в основном потому, что вы увеличиваете время, затрачиваемое на ваш запрос, на минимум x, что обычно достаточно, чтобы заставить его выполнить setState ПОСЛЕ того, как компонент смонтирован - так эффективно вы могли бы сделать свой setState внутри componentDidMount вместо этого, а не полагаться на установку компонента в своем таймере setTimeout.

TL; DR ответ:

Вы можете setState внутри componentWillMount синхронно, хотя это не рекомендуется. В идеале любая ситуация, когда вы делаете это синхронно, вместо этого использовала бы getInitialState.

Однако использование асинхронно setState в componentWillMount крайне неразумно, так как оно откроет вам потенциальные условия гонки на основе времени, которое займет ваша задача async. Используйте componentDidMount вместо этого и используйте этот первоначальный рендер, чтобы показать загрузчик или подобное:)

Ответ 3

Пробуя это в простом компоненте, следующее работает отлично:

  getInitialState: function() {
    return {
      title: 'One'
    };
  },

  componentWillMount: function() {
    setTimeout(function(){
      this.setState({
        title: 'Two'
      });
    }.bind(this), 2000);
  },

Можете ли вы разместить точную ошибку и, возможно, стек, чтобы мы могли лучше видеть проблему, с которой вы сталкиваетесь?