Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Warning: setState(...): Cannot update during an existing state transition with redux/immutable

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>
like image 245
Jiew Meng Avatar asked Apr 09 '17 05:04

Jiew Meng


People also ask

Why do you need to use setState () to update state in React?

Always use the setState() method to change the state object, since it will ensure that the component knows it's been updated and calls the render() method.

Why we use this setState method to update state but not directly?

The answer: They're just queuessetState , and useState does not make changes directly to the state object. React this. setState , and React. useState create queues for React core to update the state object of a React component.

How do you update existing state in React?

To update our state, we use this. setState() and pass in an object. This object will get merged with the current state. When the state has been updated, our component re-renders automatically.

What happens when you update a component's state?

setState() schedules an update to a component's state object. When state changes, the component responds by re-rendering.


2 Answers

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

like image 165
cabolanoz Avatar answered Nov 02 '22 06:11

cabolanoz


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.

like image 21
fnune Avatar answered Nov 02 '22 07:11

fnune