可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 to componentWillMount
.
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>
回答1:
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 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 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 a PureComponent
(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 a div
), then that is not the cause of the problem.
回答2:
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.
回答3:
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 componentWillMount
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 the addNotification
回答4:
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.
回答5:
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 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