'React Redux: Should reducers contain any logic
I have a cart reducer function with add, update and delete cases. I also have a product array within the redux store. When there are two items added to the product array, instead of having two items, I increment the quantity value. My main question is, should the reducers include any logic i.e. determine if the products array already contains the exact product and just returns an update on quantity of product or should this behavior be handled within the presentational component checking for existing products and either adding a new product or updating the quantity?
function CartReducer (state = initialState, action) {
switch (action.type) {
case AddToCart:
return {
...state,
products: [...state.products, action.product],
totalPrice: state.totalPrice += (action.price * action.quantity)
}
case RemoveItemCart:
return {
...state,
products: [
...state.products.slice(0, action.index),
...state.products.slice(action.index + 1)
]
}
case UpdateItemQuantity:
return {
...state,
products: state.products.map((product, index) => {
if (index === action.index) {
return Object.assign({}, product, {
quantity: action.quantity
})
}
return product
})
}
default:
return state
}
}
Solution 1:[1]
Most certainly! Reducers should be pure functions, so the logic must be pure as well. This means there should be zero side effects. Side effects would include (but not limited to):
- Database requests/stores
- File IO
- REST/async calls
- Global or external mutations
- Data mutations of any sort
So reducers should never mutate the state that comes in, but return a copy with modifications only made to the copy, if that. Immutable values (like strings, numbers, undefined, etc) can be returned as-is, and so can state, if it hasn't been modified. If you need to make any changes to any inputs, however, you'd return a new copy or a new value.
With regard to the logic being called from your reducer, as long as all that code meets these requirements, then you are in-line with the Redux pattern.
Unfortunately, JavaScript doesn't have a way to know for sure when any given code has side effects (other languages do), so you should just be aware of what you are calling.
Will it break Redux if you fail? No. But things might not work exactly as you (or the Flux/Redux developers) expect.
Solution 2:[2]
No, they shouldn't contain logic
Actions gather and emit large objects that reducers split into proper locations
Instead of large objects, you may procedurally dispatch actions at each step in an action creator, which is what the top answer seems to promote (yet is impossible with plain redux)
Components: pass existing redux data into actions
Actions: gather data and emit large object, applicable to as many reducers as possible
Reducers: extract action object for granular, non-repeating, flat storage (as opposed to monolithic, redundant, nested folders). I recommend explicit
initialStatefor easy maintenance and blatantly obvious data shape (states should never change shape; if joined objects are unavailable, serialize byidrather than sometimes nesting full objects).An
idcan be thought of as a serialized memory reference or "pointer" which may have different content but can be===to itself; theidis the object reference.. serialized.*Side Effects: subsequent action listener/dispatchers, useful when you don't want to wait before emitting an initial event (sagas are subscription-based, thunks are declarative chains)
A rule of thumb is to do everything as eagerly as possible
Eager operation (early return, fail fast, hoisting state, etc) clarifies whether specific dependencies exist concurrently, and makes for easy decisions
Eager operation falls in line with “separation of concerns” and a simple theme for modular, declarative, stateful, pragmatic, legible code: declare dependencies, operate, and return
Everything else is a matter of sequencing, which should be done as eagerly as possible
A reducer is the last place you’d want to be “doing” anything. It’s effectively a “return” and should simply select data from action objects
Solution 3:[3]
Consider what you might want to do in your middleware...if you put all your logic inside your reducer then the record in the middleware will not have the business logic applied to it.
- dispatch(new UpdatePerson(person))
- run middleware and intercept the UpdatePerson action
- reducer - updates the age value for person
if you want to say, save your record in a store, then the reducer logic will run too late and you won't have access to the changed record in the state you require it in your middleware.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Brian Genisio |
| Solution 2 | |
| Solution 3 |
