Reputation: 2498
I'm trying to figure out why props (mapped from the redux store) aren't updated after calling dispatch
. For example, in the code below dispatch the RANDO action, which sets a random number. Immediately after dispatching if I access this.props.rando
it doesn't contain the updated value. Is this expected behavior, or am I doing something wrong?
// reducer.js
module.exports = (state = {}, action) => {
const newRando = Math.round(Math.random() * 100)
console.log("Reducer: rando = ", newRando);
return { rando: newRando }
}
// app.jsx
const { connect } = require('react-redux')
const React = require('react')
class App extends React.Component {
onClick() {
console.log("Clicked: rando = ", this.props.rando)
this.props.dispatch({ type: 'RANDO' })
console.log("After Dispatch: rando = ", this.props.rando)
setTimeout(() => console.log("After setTimeout: rando = ", this.props.rando), 1)
}
render() {
return (
<div>
<div onClick={() => this.onClick()}>HI</div>
</div>)
}
}
App.propTypes = {
dispatch: React.PropTypes.func.isRequired,
rando: React.PropTypes.number.isRequired,
}
const mapStateToProps = state => {
return ({
rando: state.rando
})
}
module.exports = connect(mapStateToProps)(App)
The output after I click the HI
div is
Clicked: rando = 36
Reducer: rando = 48
After Dispatch: rando = 36
After setTimeout: rando = 48
Upvotes: 0
Views: 127
Reputation: 4375
You should not use props
to access state. Think of state at different times as frames in as movie. They aren't attached and can't communicate with each other.
The props you access should be thought of as strictly immutable. Think of your React jsx as a function that renders the entire app from scratch. If anything changes, your entire app is replaced with the new version.
In reality this isn't what happens, but this is because without Redux, you need a place to store the state, and because actually rendering the entire app would not be as performant as using an optimized render path. But you should still think of it as if the entire app were rerendered from scratch because it is the main benefit of using React.
Your render function runs strictly within a frame / state and if it tries to use setState
then React will complain. If it tries to dispatch
an action, Redux or React will complain. You shouldn't try changing the frame you are already rendering.
The onClick
code runs between frame renders. This means that it always sees the old frame. The new frame isn't ready yet and won't be ready until the next render. Therfore you can't see it. Redux and React will collude to fire up a new render cycle soon enough.
As other answers indicated, you can use Redux-Thunk to move the code of the onClick
to a thunked action creator, where you can access the before and after state.
But instead, consider doing away with needing the before and after states. You can just dispatch
an action that takes care of modifying the state (immutably of course) inside the reducer. The reducer has full access to the state.
Then leave it up to Redux, React-Redux, and React to "make it happen" on your screen. There's some magic in there and it's not synchronous. But you don't have to worry about it.
Upvotes: 1
Reputation: 39287
If you really need the new 'rando' value after dispatching an action (before the re-render) you might want to look into using thunks
Read this first when deciding if you actually need thunks:
How to dispatch a Redux action with a timeout?
const thunk = ReduxThunk.default;
const { createStore, applyMiddleware, bindActionCreators } = Redux;
const { connect, Provider } = ReactRedux;
const actions = {
nextRandom() {
return (dispatch, getState) => {
dispatch({
type: 'RANDO',
});
return getState().rando;
};
}
};
const reducer = (state = {rando: Math.round(Math.random() * 100)}, action) => {
switch (action.type) {
case 'RANDO': {
const newRando = Math.round(Math.random() * 100);
console.log("Reducer: rando =", newRando);
return {...state, rando: newRando };
}
default:
return state;
}
};
const store = createStore(reducer, applyMiddleware(thunk));
const render = () => {
ReactDOM.render(<Root />, document.getElementById("root"));
};
store.subscribe(render);
class App extends React.Component {
onClick() {
console.log("Clicked: rando =", this.props.rando)
const returned = this.props.nextRandom();
console.log('After Dispatch returned: rando =', returned);
console.log("After Dispatch props: rando =", this.props.rando)
setTimeout(() => console.log("After setTimeout: rando =", this.props.rando), 1)
}
render() {
return (
<div>
<div onClick={() => this.onClick()}>HI</div>
<div>{this.props.rando}</div>
</div>
);
}
}
App.propTypes = {
rando: React.PropTypes.number.isRequired,
nextRandom: React.PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
rando: state.rando,
});
const mapDispatchToProps = dispatch => ({
nextRandom: bindActionCreators(actions.nextRandom, dispatch),
});
const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App);
const Root = () => (
<Provider store={store}>
<ConnectedApp />
</Provider>
);
render();
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.1.0/redux-thunk.js"></script>
<div id="root"></div>
Upvotes: 1
Reputation: 885
this.props.rando
changes its value only after the App
receives new props. You could get the new value in the componentWillReceiveProps
:
componentWillReceiveProps(nextProps){
console.log(this.props.rando); //orignal value
console.log(nextProps.rando); //changed value
}
render() {
console.log(this.props.rando); //changed value. Render is invoked after componentWillReceiveProps. At this time, props has set to the new value.
return (
<div>
<div onClick={() => this.onClick()}>HI</div>
</div>)
}
Upvotes: 2