警告:setState(...):在使用 redux/immutable 的现有状态转换期间无法更新

IT技术 javascript reactjs redux react-redux
2021-05-21 05:07:20

我收到错误

警告:setState(...):无法在现有状态转换期间更新(例如在render组件内部或其他组件的构造函数中)。Render 方法应该是 props 和 state 的纯函数;构造函数的副作用是一种反模式,但可以移至componentWillMount.

我发现原因是

const mapStateToProps = (state) => {
  return {
    notifications: state.get("notifications").get("notifications").toJS()
  }
}

如果我没有在那里返回通知,它就可以工作。但这是为什么呢?

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

更新

在进一步调试时,我发现以上可能不是根本原因,我可以保留通知,但我需要删除dispatch(push("/domains"))我的重定向。

这是我登录的方式:

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)
  }
}

我发现调度导致警告,但为什么呢?我的域页面目前没有太多内容:

import {connect} from "react-redux"
import DomainsIndex from "./DomainsIndex.jsx"

export default connect()(DomainsIndex)

域名索引

export default class DomainsIndex extends React.Component {
  render() {
    return (
      <div>
        <h1>Domains</h1>
      </div>
    )
  }
}

更新 2

我的App.jsx<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>
4个回答

您会dispatch(push('/domains'))遇到其他调度,这些调度为连接组件(可能是关心通知的组件)设置状态,这些组件在push生效后重新安装/卸载

作为一种变通方法和概念证明,尝试dispatch(push('/domains'))使用嵌套的 zero-second推迟调用setTimeout这应该push在任何其他操作完成后执行(即希望是渲染):

setTimeout(() => dispatch(push('/domains')), 0)

如果可行,那么您可能需要重新考虑您的组件结构。我想Notifications是一个您想要安装一次并在应用程序的整个生命周期内保持在那里的组件。尽量避免通过将它放在组件树中的更高位置来重新安装它,并使其成为PureComponent(这里是文档)。此外,如果您的应用程序的复杂性增加,您应该考虑使用一个库来处理像redux-saga这样的异步功能

尽管此警告通常是由于错误的操作调用(例如在 render: 上调用操作:onClick={action()}而不是作为 lambda: 传递onClick={() => action()})而出现,但如果您的组件看起来像您提到的(只是渲染 a div),那么这不是原因的问题。

我过去遇到过这个问题,并设法使用redux-batched-actions解决了它

当您一次分派多个操作并且您不确定何时触发更新时,它对于像您这样的用例非常有用,这样,多个操作将只有一个分派。在你的情况下,后续似乎addNotification很好,但第三个太多了,可能是因为它与历史api交互。

我会尝试做一些类似的事情(假设你的 setTimeout 当然会被 api 调用取代):

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)
  }
}

请注意,您必须enableBatching在商店创建中下载包和减速器。

发生这种情况的原因是您何时调用doLogin,如果您从constructor. 如果是这种情况,请尝试将其移动到,componentWillMount尽管您应该从按钮调用此方法或在登录表单中输入命中。

这已经被记录在构造函数中不应该发生变异。如果这不是你介意评论中doLogin各行要知道问题的根源到底是哪行给国家的问题,我的猜测是,要么在pushaddNotification

没有足够的信息来给出确定的答案。但是可以肯定的是,当您尝试使用setState内部render方法时会引发此警告

大多数情况下,当您调用处理程序函数而不是将它们作为 props 传递给 child 时,就会发生这种情况Component就像它发生在这里这里一样

所以我的建议是其双重检查Components中正在上呈现的/domains路线,你是如何传递onChangeonClick等处理程序对他们和他们的孩子。