react-redux: Triggering async data request in ES6 "constructor()" or "componentWillMount()" or "componentDidMount()"
(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.
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 25
- Comments: 24 (11 by maintainers)
Commits related to this issue
- WIP: Failing test for #129 — committed to esamattis/react-redux by esamattis 9 years ago
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
dispatchwhich caused mountingUserswhich caused anotherdispatchin its the constructor. So,dispatchinsideconnect(Users)caused asetStateinsideconnect(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.Is the warning coming from React Redux’s usage of state? Or are you putting data from store into the state manually? Can you provide an example reproducing the issue?
That said, using
componentWillMountis the way to go. Why React doesn’t encouragesetState()calls inside constructor, I’m not sure, but it’s better to ask in React issues than here.Sorry, I meant a unit test.
This should launch our test runner.
Then you can add the test emitting the warning to
connect.spec.js(see other tests there for inspiration). To make it easier, you can writeit.only()instead ofit()so only your test runs.Does this make sense?
Thanks for the reply, Dan. I posted the question here because the problem seems to be originating in redux-thunk.
I can do my best to piece together an example. This isn’t exactly the code I have executing, but it is, I think, a close enough approximation to illustrate the issue.
index.js:
app.js:
getusers.js
actions.js
reducers.js
I hope the sample code is somewhat useful. What I found somewhat interesting was that in the
fetchUsersFromServer()call, it didn’t matter if I actually made an async request or not, as I indicate in the comment above. I guess maybe that means this is just me not understanding the React lifecycle properly. Again, I posted this problem here because the problem gets exposed within the thunk, though I realize that doesn’t mean the thunk itself is at fault.So if the real answer here is "make async calls in
componentWillMount(), then I’m happy with that answer. I suppose I’m just trying to get a better understanding of how these parts work together, and why this issue only became apparent when I upgraded to React 0.14.Thanks again!