React-Redux:Â Cannot update during an existing state transition (such as within render).
When trying out react-redux samples today, I was stuck on a strange error â actually it seemed strange to be because I couldnât understand the syntax, but whatever.
Iâm documenting it here in case I forget and make the same mistake again:
Error:
Warning: setState(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.Â
And eventually:
Uncaught RangeError: Maximum call stack size exceededÂ
Scenario:
React application that had following âshapeâ; basically, the counter increments/decrements counter state based on âcounter modeâ which is either âincâ or âdecâ:
A. App a. CounterContainer i. Counter ii. CounterModeToggle
CounterContainer is the owner of the state; a sample state object could be represented as {count: 1, mode: "inc"}.
Whenever CounterModeToggle fired its "onToggle" function (that was passed to it by CounterContainer), it worked. However, when Counter fired its "onTick" function (that basically invoked its action-creator and passed it to redux store (via the connect method of react-redux), it gave the error mentioned above.
Explanation:
Searching for the error turned up this SO answer: https://stackoverflow.com/questions/27172358/reactjs-passing-methods-as-props-to-child -â it is precise in its explanation about why it happened in the first place.
What I understood is:
onClick of ReactComponent expects a function that will be fired when the synthetic event fires.
When I use the form onClick={onTick(currentMode)} it means:
execute onTick with currentMode as the parameter when rendering this button. It causes the redux-store to mutate its state (that's how the reducet was written) which causes a re-render of the same react component! This is completely not what I was trying to do.
The correct way to say "execute this function when onClick synthetic event is fired" is to either:
wrap the invocation into another function (this is what onClick={() => onTick(currentMode)} does) or
use the bind function to supply the parameter. This keeps the onTick to be a function being passed as parameter as opposed to being invoked whenever being renderd (which is what onClick={onTIck(..)} does!
I personally feel that the bind way is more natural but I'm not sure if there are an upsides/downsides of using it one way or other.
Code:
components/App.js
import React from 'react' import CounterContainer from '../containers/CounterContainer.js'; const App = () => ( <CounterContainer /> ) export default App
container/CounterContainer.js
import React from 'react'; import { connect } from 'react-redux' import Counter from '../components/Counter.js' import ToggleCounterMode from '../components/ToggleCounterMode.js' import { counterTickAction, counterToggleAction } from '../actions' const PureCounterContainer = ({ currentCounter, onTick, currentMode, onToggle }) => ( <div className="counter-container"> <Counter currentMode={currentMode} currentCounter={currentCounter} onTick={onTick} /> <ToggleCounterMode currentMode={currentMode} onToggle={onToggle} /> </div> ); const mapStateToProps = (state, ownProps) => { console.log('CounterContainer::mapStateToProps - state:%o, ownProps:%o', state, ownProps); return { currentCounter: state.counter, currentMode: state.mode } } const mapDispatchToProps = (dispatch, ownProps) => { console.log('CounterContainer::mapDispatchToProps - dispatch:%o, ownProps:%o', dispatch, ownProps); return { onTick: (mode) => { dispatch(counterTickAction(mode)); }, onToggle: () => { dispatch(counterToggleAction()); } } } const CounterContainer = connect( mapStateToProps, mapDispatchToProps )(PureCounterContainer) export default CounterContainer
component/Counter.js
import React from 'react'; const Counter = ({ currentMode, currentCounter, onTick }) => ( <div className="counter"> // GOOD: <p>this is a counter <a onClick={onTick.bind(null, currentMode)}>{currentMode}</a></p> // GOOD: <p>this is a counter <a onClick={() => onTick(currentMode)}>{currentMode}</a></p> // ERROR: // <p>this is a counter <a onClick={onTick(currentMode)}>{currentMode}</a></p> <p>current counter value: {currentCounter}</p> </div> ); export default Counter;















