document is not defined when attempting to setState from the return of an async call in componentWillMount

后端 未结 3 2509
余生分开走
余生分开走 2021-02-20 04:02

I grab my data in my componentWillMount call of my component [actually it\'s in a mixin, but same idea]. After the ajax call returns, I attempt to setState, but I get the error

3条回答
  •  广开言路
    2021-02-20 04:46

    I've actually encountered a similar situation before. I assume the error you encountered was something like this:

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

    The error is caused by the fact that, in React components, you cannot set state before the component is mounted. So, instead of attempting to set the state in componentWillMount, you should do it in componentDidMount. I typically add an .isMounted() check, just for good measure.

    Try something like this:

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


    EDIT: Forgot to mention ... If the component gets "unmounted" before the async operation completes, you may also encounter an error.

    This can be easily handled if the async operation is "cancelable". Assuming your request() above is something like a superagent request (which are cancelable), I would do the following to avoid any potential errors.

    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();
    }
    


    EDIT #2: In one of our comments you mentioned your intent was to create an isomorphic solution that could load state asynchronously. While this is beyond the scope of the original question, I will suggest you check out react-async. Out-of-the-box, it provides 3 tools that can help you achieve this goal.

    1. getInitialStateAsync - this is provided via a mixin, and it allows a component to fetch state data asyncrhonously.

      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() {
           ... 
        }
      });
      
    2. renderToStringAsync() - which allows you to render server side

      ReactAsync.renderToStringAsync(
        ,
        function(err, markup, data) {
          res.send(markup);
        })
      );
      
    3. injectIntoMarkup() - which will inject the server state, along with the markup to ensure it's available client-side

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

    react-async provides far more functionality than this. You should check out the react-async documentation for a full list of its features, and a more comprehensive explanation of the ones I briefly describe above.

提交回复
热议问题