React-Redux and Connect - Why is my state not upda

2019-02-20 12:34发布

问题:

I am new to redux and am writing a simple voting front end which allows the user to vote on their favourite framework (Angular, React, Vue). When a user clicks on a framework they would like to vote for, I intend to increment the votes in the store by one.

I'm using combineReducers and connect to facilitate this. However, after each click, the state is always one vote behind. After one vote the state is at 0. After 2 clicks, the vote state is at 1, etc.

Here is my index.js file wherein I create my store and render the app

//index.js
const combinedReducers = combineReducers({
    "votes": votesReducer,
    "currentPercentages": currentPercentageReducer,
    "pastPercentages": pastPercentageReducer
});

let store = createStore(combinedReducers);

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>, 
    document.getElementById('root')
);

Here is my action

export function voteAction(framework){
    return {
        type: "VOTE",
        payload: framework
    }
}

Below is the relevant reducer for updating the vote state

const initialVoteState = { angular: 0, react: 0, vue: 0 };

export function votesReducer(state=initialVoteState, action){
    switch(action.type){
        case "VOTE":
            if (action.payload === 'angular'){
                return Object.assign({}, state, {
                    angular: state.angular + 1
                });
            } else if (action.payload === 'react'){
                return Object.assign({}, state, {
                    react: state.react + 1
                });
            } else {
                return Object.assign({}, state, {
                    vue: state.vue + 1
                });
            }
        default:
            return state;
    }

}

Finally, here is the component I have which has the click handler to facilitate casting a vote and updating the state accordingly.

@connect(state => {
    return {
        votes: {
            angular: state.votes.angular,
            react: state.votes.react,
            vue: state.votes.vue
        },
        currentPercent: {
            angular: state.currentPercentages.angular,
            react: state.currentPercentages.react,
            vue: state.currentPercentages.vue
        },
        pastPercent: {
            angular: state.pastPercentages.angular,
            react: state.pastPercentages.react,
            vue: state.pastPercentages.vue
        }
    }
})
export default class InfoArea extends Component {
    constructor(props){
        super(props);
        this.votedAngular = this.votedAngular.bind(this);
        this.votedReact = this.votedReact.bind(this);
        this.votedVue = this.votedVue.bind(this);
    }

    votedAngular(){
        this.props.dispatch(voteAction('angular'));
        console.log(this.props.votes.angular);
        //the log above will output 0 after the first vote is cast, 1
        after the second vote is cast etc. Why is this the case? Should 
        it not be set to the correct value here since the action has 
        already been dispatched?

    }

    votedReact(){
        this.props.dispatch(voteAction('react'));
    }

    votedVue(){
        this.props.dispatch(voteAction('vue'));
    }

    render(){
        return (
        <div className="info-container">
            <img 
                style={{"marginTop":"25px"}} 
                className="app-img" 
                src={require("../../public/brainbulb.svg")}>
            </img>
            <h2 className="wide">What is your favourite front-end framework in 2017?</h2>
            <h4 style={{"marginTop": "0"}}>Click an image below to vote!</h4>
            <div className="row">
                <div className="images-container">
                    <img
                        onClick={this.votedAngular} 
                        className="framework-logo" 
                        src={require("../../public/angular.png")}>
                    </img>
                    <img 
                        onClick={this.votedReact}
                        className="framework-logo" 
                        src={require("../../public/react.png")}>
                    </img>
                    <img 
                        onClick={this.votedVue}
                        className="framework-logo"
                        src={require("../../public/vue.png")}
                    ></img>
                </div>
            </div>
        </div>
        );
    }
}

Also worth noting that when I cast a vote and view my store state in the browser console it is accurate. Yet, my component remains one vote behind. Any help would be greatly appreciated.

回答1:

The reason you are not able to the the updated state immediately is because the operation of updating the store is async and writing

votedAngular(){
    this.props.dispatch(voteAction('angular'));
    console.log(this.props.votes.angular);
}

means that you are trying to check for the value as soon as the action is triggered. If you want to test the value, I would suggest you do it in the componentWillReceiveProps function

componentWillReceiveProps(nextProps) {
   console.log(nextProps.votes.angular);
}

However, for such operations where you need to test for redux values, redux-devtools-extension is an ideal tool.