I am getting the error
Warning: setState(...): Cannot update during an existing state transition (such as within
render
or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved tocomponentWillMount
.
I found the cause to be
const mapStateToProps = (state) => {
return {
notifications: state.get("notifications").get("notifications").toJS()
}
}
If I do not return notifications there it works. But why is that?
import {connect} from "react-redux"
import {removeNotification, deactivateNotification} from "./actions"
import Notifications from "./Notifications.jsx"
const mapStateToProps = (state) => {
return {
notifications: state.get("notifications").get("notifications").toJS()
}
}
const mapDispatchToProps = (dispatch) => {
return {
closeNotification: (notification) => {
dispatch(deactivateNotification(notification.id))
setTimeout(() => dispatch(removeNotification(notification.id)), 2000)
}
}
}
const NotificationsBotBot = connect(mapStateToProps, mapDispatchToProps)(Notifications)
export default NotificationsBotBot
import React from "react"
class Notifications extends React.Component {
render() {
return (
<div></div>
)
}
}
export default Notifications
UPDATE
On further debugging I found that, the above may not be the root cause after all, I can have the notifications stay but I need to remove dispatch(push("/domains"))
my redirect.
This is how I login:
export function doLogin (username, password) {
return function (dispatch) {
dispatch(loginRequest())
console.log("Simulated login with", username, password)
setTimeout(() => {
dispatch(loginSuccess(`PLACEHOLDER_TOKEN${Date.now()}`))
dispatch(addNotification({
children: "Successfully logged in",
type: "accept",
timeout: 2000,
action: "Ok"
}))
dispatch(push("/domains"))
}, 1000)
}
}
I find that the dispatch causes the warning, but why? My domains page have nothing much currently:
import {connect} from "react-redux"
import DomainsIndex from "./DomainsIndex.jsx"
export default connect()(DomainsIndex)
DomainsIndex
export default class DomainsIndex extends React.Component {
render() {
return (
<div>
<h1>Domains</h1>
</div>
)
}
}
UPDATE 2
My App.jsx
. <Notifications />
is what displays the notifications
<Provider store={store}>
<ConnectedRouter history={history}>
<Layout>
<Panel>
<Switch>
<Route path="/auth" />
<Route component={TopBar} />
</Switch>
<Switch>
<Route exact path="/" component={Index} />
<Route path="/auth/login" component={LoginBotBot} />
<AuthenticatedRoute exact path="/domains" component={DomainsPage} />
<AuthenticatedRoute exact path="/domain/:id" component={DomainPage} />
<Route component={Http404} />
</Switch>
<Notifications />
</Panel>
</Layout>
</ConnectedRouter>
</Provider>
There is not enough info to give a certain answer. But what is for sure is that this warning is raised when you tries to
setState
insiderender
method.Most often it happens when you are calling your handler functions instead of passing them as props to child
Component
. Like it happened here or here.So my advice is to double check which
Components
are being rendered on your/domains
route and how you are passingonChange
,onClick
, etc. handlers to them and to their children.I had this issue in the past, and managed to resolve it using redux-batched-actions.
It's very useful for use-cases like yours when you dispatch multiples actions at once and you're unsure of when the updates will get triggered, with this, there will be only one single dispatch for multiple actions. In your case it seems that the subsequent
addNotification
is fine, but the third one is too much, maybe because it interacts with the history api.I would try to do something like (assuming your setTimeout will be replaced by an api call of course):
Note that you'll have to download the package and
enableBatching
your reducer in your store creation.The reason this is happening is when are you calling the
doLogin
, if you are calling it from within aconstructor
. If this is the case try moving it tocomponentWillMount
although you should be calling this method from a button or enter hit in the login form.This have been documented in constructor should not mutate If this is not the root of the problem you mind commented each line in doLogin to know exactly which line giving the state problem, my guess would be either the
push
or theaddNotification
Your
dispatch(push('/domains'))
comes along other dispatches that set the state for a connected component (presumably one that cares about notifications) that gets remounted/unmounted after thepush
takes effect.As a workaround and proof of concept, try defering the
dispatch(push('/domains'))
call with a nested zero-secondsetTimeout
. This should execute thepush
after any of the other actions finish (i.e. hopefully a render):If that works, then you might want to reconsider your component structure. I suppose
Notifications
is a component you want to mount once and keep it there for the lifetime of the application. Try to avoid remounting it by placing it higher in the component tree, and making it aPureComponent
(here are the docs). Also, if the complexity of your application increases, you should consider using a library to handle async functionality like redux-saga.Even though this warning usually appears because of a misplaced action call (e.g. calling an action on render:
onClick={action()}
instead of passing as a lambda:onClick={() => action()}
), if your components look like you've mentioned (just rendering adiv
), then that is not the cause of the problem.In a react component when you call
setState({...})
, it causes the component to re-render and call all the life cycle methods which are involved in re-rendering of the component andrender
method is one of themWhen in
render
if you callsetState({...})
it will cause a re-render and render will be called again and again hence this will trigger an infinite loop inside the javascript eventually leading to crashing of the appHence, not only in
render
but in any life-cycle method which is a part of re-render processsetState({...})
method shouldn't be called.In your case the code might be triggering an update in the redux state while the code is still rendering and hence this causes re-render and react shows error