警告:setState(...):在使用redux / immutable

时间:2017-04-09 05:50:23

标签: javascript reactjs redux react-redux

我收到错误

  

警告:setState(...):无法在现有状态转换期间更新(例如在render或其他组件的构造函数中)。渲染方法应该是道具和状态的纯函数;构造函数副作用是反模式,但可以移动到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)

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>

5 个答案:

答案 0 :(得分:6)

dispatch(push('/domains'))出现了其他调度,这些调度设置了在push生效后重新安装/卸载的已连接组件的状态(可能是关注通知的状态)。

作为解决方法和概念验证,请尝试使用嵌套的零秒dispatch(push('/domains'))推迟setTimeout调用。这应该在任何其他操作完成后执行push(即希望渲染):

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

如果可行,那么您可能需要重新考虑组件结构。我想Notifications是您想要挂载一次的组件,并在应用程序的生命周期内保留它。尽量避免重新安装,方法是将其放在组件树中,并将其设为PureComponent(此处为docs)。此外,如果应用程序的复杂性增加,您应该考虑使用库来处理redux-saga等异步功能。

即使这个警告通常是因为错误的动作调用而出现(例如,在渲染上调用一个动作:onClick={action()}而不是传递为lambda:onClick={() => action()}),如果你的组件看起来像你一样&#39 ;提到(只是渲染div),那么这不是问题的原因。

答案 1 :(得分:4)

我过去遇到过这个问题,并设法使用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 reducer。

答案 2 :(得分:1)

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

这已在constructor should not mutate中记录。如果这不是问题的根源,您可以在doLogin中注释每行确切地知道给出状态问题的哪一行,我的猜测可能是pushaddNotification

答案 3 :(得分:0)

没有足够的信息来给出一定的答案。但可以肯定的是,当您在setState方法内尝试render时会出现此警告。

大多数情况下,当您调用处理程序函数而不是将它们作为道具传递给子Component时,就会发生这种情况。就像它发生herehere

所以我的建议是仔细检查Components路线上呈现的/domains以及您如何将onChangeonClick等处理程序传递给他们以及给他们的孩子。

答案 4 :(得分:0)

在调用setState({...})时的react组件中,它会导致组件重新呈现并调用组件重新呈现中涉及的所有生命周期方法,并且render方法是一个他们

如果你在render调用setState({...}),它会导致重新渲染,渲染会一次又一次地被调用,因此这将触发javascript中的无限循环,最终导致应用程序崩溃

因此,不仅在render中,而且在任何生命周期方法中都不应该调用重新渲染过程setState({...})方法。

在您的情况下,代码可能在代码仍在呈现时触发redux状态的更新,因此这会导致重新呈现并且反应显示错误