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

后端 未结 3 2510
余生分开走
余生分开走 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:36

    It's not a good idea to be doing something asynchronous inside componentWillMount. You should really be doing this in the componentDidMount because if the first task a component does is to fetch some data - you're probably going to want it to show a spinner or some kind of loading notifier before it gets that data.

    As a result I personally don't ever do what you're doing, opting for componentDidMount every time. Then you can set your initial state so that that first mounting render shows a loading spinner or some other kind of initial state to the user. The ajax fires, and you update once you get a response. This way you know that you're handling cases where your user is on a crappy connection, such as mobile with bad reception or such, giving a good UX by letting the user know that a component is loading some data which is why they don't see anything yet.

    This all being said, why do you get an error when performing some asynchronous functions within componentWillMount - because if you just called this.setState inside the lifecycle function itself, it would work fine right? This is down to a quirk of how React works, it's been around since at least React 0.11 as far as I'm aware. When you mount a component, executing this.setState synchronously inside componentWillMount works just fine (although there's a bug in 0.12.x where any function callback passed to setState inside componentWillMount will not be executed). This is because React realises that you're setting the state on a component which isn't yet mounted - something that you can't usually do - but it allows it within lifecycle functions like componentWillMount specially. However when you asynchronize that setState call, it's no longer treated specially and the normal rules apply - you cannot setState on a component which is not mounted. If your ajax request returns very quickly, it's entirely possible that your setState call is happening AFTER the componentWillMount phase but BEFORE the component has actually mounted - and you get an error. If in fact your ajax request wasn't as fast as it evidently is, say it took a second or more, then you probably wouldn't notice an error since it's highly likely that your component mounted fully within a second and so your setState call becomes valid by normal rules again. But you're basically giving yourself a race condition here, be safe and use componentDidMount instead - as it's also better for other reasons I talked about above.

    Some people are saying you can do this inside a setTimeout and they are correct, but it's basically because you're increasing the time taken for your request to a minimum of x, which is usually enough to force it to execute setState AFTER the component has mounted - so effectively you might as well have been doing your setState inside componentDidMount instead and not rely on the component mounting within your setTimeout timer.

    TL;DR answer:

    You can setState inside componentWillMount synchronously, although it's not recommended. Ideally any situation where you do this synchronously, you would use getInitialState instead.

    However using setState asynchronously in componentWillMount is extremely unwise as it will open you to potential race conditions based on the time your async task takes. Use componentDidMount instead and use that initial render to show a loading spinner or similar :)

提交回复
热议问题