Scott
Scott

Reputation: 442

Why is my reducer returning the wrong state, despite everything looking correct when I log it out?

I am new to redux and trying to build a simple app in which users can vote on their favourite front end framework. I want to have a workflow where the user clicks to cast a vote, this dispatches a vote action. After the vote action a different action is fired to calculate the percentages of total votes for each framework.

My store/reducer setup looks as such:

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

let store = createStore(combinedReducers, applyMiddleware(thunk));
store.subscribe(() => {
    console.log('store state is', store.getState())
})

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

My click handlers fire the intital "cast vote" action which in turn fires the "calculate percentages" action. They look like this:

//click handlers

votedAngular(){
    this.props.dispatch(castVote('angular'));
}

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

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

The action creators:

const voteAction = (framework) => {
    return {
        type: "VOTE",
        payload: framework
    }
}

const updateCurrentPercentages = (voteState) => {
    return {
        type: "UPDATE_CURRENT_PERCENTAGES",
        payload: voteState
    }
}

export const castVote = (framework) => (dispatch, getState) => {
    dispatch(voteAction(framework));
    dispatch(updateCurrentPercentages(getState().votes)); //this does pass in current vote state correctly   
}

Finally - the heart of the problem. My reducer which handles updating the current vote percentages.

export function currentPercentageReducer(state=intitialPercentages, action){
    switch (action.type){
        case "UPDATE_CURRENT_PERCENTAGES":

            let totalVotes = action.payload.angular + action.payload.react + action.payload.vue;

            //totalVotes, and individual percentage calculations are 
            //all console.logged correctly. 
            //EX. if there is one vote for each of the three frameworks
            //I get totalVotes = 3, pctAngular = 1/3, pctReact = 1/3, 
            //pctVue = 1/3

            console.log('totalVotes', totalVotes)
            console.log('pctAngular', action.payload.angular + ' / ' + totalVotes);
            console.log('pctReact', action.payload.react + ' / ' + totalVotes);
            console.log('pctVue', action.payload.vue + ' / ' + totalVotes);

            let pctAngular = Math.floor(action.payload.angular / totalVotes) * 100;
            let pctReact = Math.floor(action.payload.react / totalVotes) * 100;
            let pctVue = Math.floor(action.payload.vue / totalVotes) * 100;

            //as soon as I vote for two or more different options
            //the console.log below always returns 0,0,0

            console.log(pctAngular, pctReact, pctVue);

            //returns the correct state when you only vote for one 
            //option, but when you vote for two or more returns
            // {angular:0, react:0, vue:0}

            return Object.assign({}, state, {
                angular: pctAngular,
                react: pctReact,
                vue: pctVue
            });

        default:
            return state
    }
}

Could someone please explain what is happening here?

Upvotes: 0

Views: 265

Answers (1)

rgon
rgon

Reputation: 396

What I suspect is happening here is that Math.floor always returns 0 for fractions. On your first vote you get Math.floor(1/1) = 1 but on your next vote you might get Math.floor(1/2) = 0. Can you confirm that if your first two votes are for the same framework you see correct results? Try using Math.round(num * 100) / 100 instead.

Upvotes: 2

Related Questions