I am getting the error
Warning: setState(...): Cannot update during an existing state transition (such as within
render
or another ponent's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved toponentWillMount
.
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 ponent={TopBar} />
</Switch>
<Switch>
<Route exact path="/" ponent={Index} />
<Route path="/auth/login" ponent={LoginBotBot} />
<AuthenticatedRoute exact path="/domains" ponent={DomainsPage} />
<AuthenticatedRoute exact path="/domain/:id" ponent={DomainPage} />
<Route ponent={Http404} />
</Switch>
<Notifications />
</Panel>
</Layout>
</ConnectedRouter>
</Provider>
I am getting the error
Warning: setState(...): Cannot update during an existing state transition (such as within
render
or another ponent's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved toponentWillMount
.
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 ponent={TopBar} />
</Switch>
<Switch>
<Route exact path="/" ponent={Index} />
<Route path="/auth/login" ponent={LoginBotBot} />
<AuthenticatedRoute exact path="/domains" ponent={DomainsPage} />
<AuthenticatedRoute exact path="/domain/:id" ponent={DomainPage} />
<Route ponent={Http404} />
</Switch>
<Notifications />
</Panel>
</Layout>
</ConnectedRouter>
</Provider>
Share
Improve this question
edited Apr 19, 2017 at 9:53
Jiew Meng
asked Apr 9, 2017 at 5:50
Jiew MengJiew Meng
88.3k192 gold badges523 silver badges832 bronze badges
7
- 3 Can you upload the code to a repo for us to play around with it? It is kind of hard to figure out with this setup. – fnune Commented Apr 16, 2017 at 14:07
- As a side note, rather than using .get().get() you should use .getIn([]). – David Bradshaw Commented Apr 17, 2017 at 6:37
-
1
In my experience this is not about the structure of the actions but more about the place from which the actions are triggered. Typically you see this error when triggering an action from
ponentWillMount
or fromrender
. Even by mistake, e.g. when writingonClick={action()}
instead ofonClick={action}
. – Sulthan Commented Apr 17, 2017 at 17:26 -
Can you go line-by-line in the action creator
doLogin
to see which one is plaining, could it be theaddNotification
or thepush
method, right now there is not that much information to fully understand what is going on. – cabolanoz Commented Apr 18, 2017 at 22:20 - @cabolanoz its the push. Without that, I dont get the warning. – Jiew Meng Commented Apr 19, 2017 at 9:54
5 Answers
Reset to default 6Your dispatch(push('/domains'))
es along other dispatches that set the state for a connected ponent (presumably one that cares about notifications) that gets remounted/unmounted after the push
takes effect.
As a workaround and proof of concept, try defering the dispatch(push('/domains'))
call with a nested zero-second setTimeout
. This should execute the push
after any of the other actions finish (i.e. hopefully a render):
setTimeout(() => dispatch(push('/domains')), 0)
If that works, then you might want to reconsider your ponent structure. I suppose Notifications
is a ponent 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 ponent tree, and making it a PureComponent
(here are the docs). Also, if the plexity 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 ponents look like you've mentioned (just rendering a div
), then that is not the cause of the problem.
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):
import { batchActions } from 'redux-batched-actions'
export function doLogin (username, password) {
return function (dispatch) {
dispatch(loginRequest())
console.log("Simulated login with", username, password)
setTimeout(() => {
dispatch(batchActions([
loginSuccess(`PLACEHOLDER_TOKEN${Date.now()}`),
addNotification({
children: "Successfully logged in",
type: "accept",
timeout: 2000,
action: "Ok"
}),
push("/domains")
]))
}, 1000)
}
}
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 a constructor
. If this is the case try moving it to ponentWillMount
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 mented each line in doLogin to know exactly which line giving the state problem, my guess would be either the push
or the addNotification
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
inside render
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 passing onChange
, onClick
, etc. handlers to them and to their children.
In a react ponent when you call setState({...})
, it causes the ponent to re-render and call all the life cycle methods which are involved in re-rendering of the ponent and render
method is one of them
When in render
if you call setState({...})
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 app
Hence, not only in render
but in any life-cycle method which is a part of re-render process setState({...})
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