I have some logic that I've put in the reducer which I'm thinking should be possibly put in the Action and passed down?
Is it best practice to put this sort of stuff in the actions or reducer?
Working example here.
Reducer code:
function Card() {
this.card = (Math.random()*4).toFixed(0);
}
Card.prototype = {
getRandom: function(){
var card;
//console.log(this.card)
switch (this.card) {
case '1':
card = 'heart';
break;
case '2':
//card = 'diamonds';
card = 'heart'; // weight the odds abit
break;
case '3':
card = 'club';
break;
case '4':
card = 'spade';
break;
default:
card = 'heart';
break;
}
return card;
}
}
var dealer = {
deal: function(){
var results = [];
for(var i = 0; i <4; i++){
var card = new Card();
results.push(card.getRandom());
}
console.log(results);
return results;
}
}
const initialState = {
count: 0,
data: []
}
function counter (state = initialState, action) {
let count = state.count
switch (action.type) {
case 'increase':
return Object.assign({}, state, {
data: dealer.deal(),
count: state.count+1
})
default:
return state
}
}
Your reducer must be pure. Currently it is not pure. It calls deal()
which calls getRandom()
which relies on Math.random()
and thus is not pure.
This kind of logic (“generating data”, whether randomized or from user input) should be in the action creator. Action creators don’t need to be pure, and can safely use Math.random()
. This action creator would return an action, an object describing the change:
{
type: 'DEAL_CARDS',
cards: ['heart', 'club', 'heart', 'heart']
}
The reducer would just add (or replace?) this data inside the state.
In general, start with an action object. It should describe the change in such a way that running the reducer with the same action object and the same previous state should return the same next state. This is why reducer cannot contain Math.random()
calls—they would break this condition, as they would be random every time. You wouldn't be able to test your reducer.
After you figure out how the action object looks (e.g. like above), you can write the action creator to generate it, and a reducer to transform state and action to the next state. Logic to generate an action resides in action creator, logic to react to it resides in the reducer, reducer must be pure.
Finally, don’t use classes inside the state. They are not serializable as is. You don’t need a Card
class. Just use plain objects and arrays.
My understanding is that actions should be simple objects that contain two things: (i) the action type and (ii) what changed (i.e. the new data).
Reducers on the other hand, are pure functions that take actions and the previous app state as inputs and return the new app state. How they accomplish this is up to you. You can add whatever logic necessary to take the combination of previous state + action and return the new state as long as you don't mutate data outside your reducer function(s).
As for your code specifically, I'm not sure the deal()
function belongs in either an action or a reducer. I think a better place might be in some sort of event handler (e.g. onClick
). You could then pass the results of the deal call as an action to your reducer.
It appears then it's a best practice to have an static class that handles the first level entery points that instantiates the redux actions outside of the redux.
I suppose that makes sense to keep the store and action chains pure.
This might look like a lot of replicated code at first but when you start dispatching based on conditions or need to dispatch from multiple places it starts to open up and make sense.