Triggering async data request in ES6 "constructor()" or "componentWillMount()" or "componentDidMount()"
See original GitHub issue(I’m fairly new to React and Redux, and so I’m still trying to figure out the best coding patterns and practices, so apologies if this is obvious to everybody but me.)
I’m working on a React/Redux app which has several components which request their own data. For example, when a “/users” route is invoked, the <Users />
component gets invoked, and that component triggers an async call (via redux-thunk) which updates the Redux store with the fetched user data, and then ultimately the users data appears within the <Users />
component.
The render()
function within <Users />
is just watching for changes to this.props
. The parent of <Users />
watches the Redux store, and of course passes the user data down.
This all worked fine in React 0.13. Or at least it “worked” with no errors/warnings in the console. I updated to 0.14 late last week, and started to see the following warning in the logs:
Warning: setState(…): Cannot update during an existing state transition (such as within render
). Render methods should be a pure function of props and state.
I floundered around, and finally figured out that the problem was that my async data request was in the class constructor. Something like this:
constructor(props) {
super(props);
fetchUsers();
}
render() {
return ( do the stuff to output the user data );
}
If I moved the fetchUsers()
call to either componentDidMount()
or componentWillMount()
, everything functioned properly, and the error went away.
So my question is ultimately this: what is going on here? What is the functional difference between these three functions? My (obviously incorrect) assumption was that I should request the data when the class gets initialized, which is why I put the async call in constructor()
. Why does that not work, why do those two React lifecycle methods work, and which one of the two is preferable? React’s docs say componentDidMount()
is the right method to use, but I would think I’d want the data before the component is mounted, which make me think componentWillMount()
should be the method I use.
Issue Analytics
- State:
- Created 8 years ago
- Reactions:25
- Comments:24 (11 by maintainers)
Top GitHub Comments
Just don’t execute side effects in constructor. It’s only for initializing state (and perhaps other variables). Don’t make calls or change the state of your app from there.
A route change caused
dispatch
which caused mountingUsers
which caused anotherdispatch
in its the constructor. So,dispatch
insideconnect(Users)
caused asetState
insideconnect(App)
.Any time you call
dispatch()
, allconnect()
-ed component wrappers have theirsetState()
called so that the connected component receive that new state as their props. It’s just how React Redux works.This is not a bug in React Redux.
This happens when
dispatch()
inside one component’s constructor causes asetState()
inside another component. React keeps track of the “current owner” for such warnings—and it thinks we’re callingsetState()
inside the constructor when technically constructor causes asetState()
inside some other part of the application.I don’t think we should handle this—it’s just React trying its best do its job. The solution is, as you correctly noted, to
dispatch()
insidecomponentWillMount()
instead.