I'm making a very simple React component which shows data obtained from a remote source. Before fetching the data, it has to wait for the user to be correctly logged in. Here is how I am trying to do this:
class MyComponent extends React.Component {
componentDidUpdate () {
if (this.props.loggedIn) {
api.getData()
.then(() => {
this.props.dispatch({
type: 'gotData'
})
}, (reason) => {
this.props.dispatch({
type: 'getDataFailed'
})
})
}
}
}
In my own words, every time a part of the state relevant to this component (in this case, the loggedIn
prop) is updated, componentDidUpdate
is called and I can get the data if I see the user is logged in.
This actually works very well, except for one major problem: It seems that calling dispatch
eventually triggers componentDidUpdate
again, so I end up in an infinite loop. I don't even have to be listening for these dispatch events anywhere, the simple fact of using the dispatch
function is enough to trigger componentDidUpdate
again.
My guess is that dispatch executes my root reducer which internally uses setState
, thereby triggering the whole update
lifecycle chain again.
How is this kind of thing usually done? How can I call dispatch
from within componentDidUpdate
without ending up in an infinite loop?
One easy way to solve this is to add an additional flag, for example loggingIn
, that you set (by dispatching) before the async action and you reset after. You also need to keep track of when the login failed to differentiate this situation from when login process wasn't started (unless you want to restart the process automatically on fail):
class MyComponent extends React.Component {
componentDidUpdate () {
const { loggedIn, loggingIn, loginFailed, dispatch } = this.props;
if (!loggingIn && !loggedIn && ! loginFailed) {
dispatch({
type: 'gettingData' // this one sets loggingIn to true, loggedIn to false, loginFailed to false
});
api.getData()
.then(() => {
dispatch({
type: 'gotData' // this one sets loggingIn to false, loggedIn to true, loginFailed to false
})
}, (reason) => {
dispatch({
type: 'getDataFailed' // this one sets loggingIn to false, loggedIn to false, loginFailed to true
})
})
}
}
}
If you do not wish to set this state in redux, you can also have this control exist within your component itself:
class MyComponent extends React.Component {
componentDidUpdate () {
const { loggedIn, dispatch } = this.props;
const { loggingIn, loginFailed } = this.state;
if (!loggingIn && !loggedIn && !loginFailed) {
this.setState({
loggingIn: true
})
api.getData()
.then(() => {
this.setState({
loggingIn: false,
loginFailed: false
});
dispatch({
type: 'gotData'
})
}, (reason) => {
this.setState({
loggingIn: false,
loginFailed: true
});
dispatch({
type: 'getDataFailed'
})
})
}
}
}
You could use the loggingIn
flag to display a spinner to your user, or the loginFailed
flag to display a message, if that fits your UX.