I don't understand what reduce-reducers is meant for. Should it be used in the case that I have 2 reducer functions containing the same action?
function reducerA(state, action){
switch(action.type):
...
case 'SAME_ACTION': {...state, field: state.field+1}
}
function reducerB(state, action){
switch(action.type):
...
case 'SAME_ACTION': {...state, field: state.field*2}
}
So if I call reduceReducer on reducerA and reducerB and action 'SAME_ACTION' is invoked for {field: 0} then I would have a next state {field: 2}?
Also it seems to me that it kind of concatenates reducers (meaning merging them under one key).
Am I right or does reduceReducer serve a different purpose?
The difference is:
combineReducers creates nested state
reduceReducers creates flat state
Consider following reducers. There are no action types to make things simpler:
// this reducer adds a payload to state.sum
// and tracks total number of operations
function reducerAdd(state, payload) {
if (!state) state = { sum: 0, totalOperations: 0 }
if (!payload) return state
return {
...state,
sum: state.sum + payload,
totalOperations: state.totalOperations + 1
}
}
// this reducer multiplies state.product by payload
// and tracks total number of operations
function reducerMult(state, payload) {
if (!state) state = { product: 1, totalOperations: 0 }
if (!payload) return state
// `product` might be undefined because of
// small caveat in `reduceReducers`, see below
const prev = state.product || 1
return {
...state,
product: prev * payload,
totalOperations: state.totalOperations + 1
}
}
combineReducers
Each reducer gets an independent piece of state (see also http://redux.js.org/docs/api/combineReducers.html):
const rootReducer = combineReducers({
add: reducerAdd,
mult: reducerMult
})
const initialState = rootReducer(undefined)
/*
* {
* add: { sum: 0, totalOperations: 0 },
* mult: { product: 1, totalOperations: 0 },
* }
*/
const first = rootReducer(initialState, 4)
/*
* {
* add: { sum: 4, totalOperations: 1 },
* mult: { product: 4, totalOperations: 1 },
* }
*/
// This isn't interesting, let's look at second call...
const second = rootReducer(first, 4)
/*
* {
* add: { sum: 8, totalOperations: 2 },
* mult: { product: 16, totalOperations: 2 },
* }
*/
// Now it's obvious, that both reducers get their own
// piece of state to work with
reduceReducers
All reducers share the same state
const addAndMult = reduceReducers(reducerAdd, reducerMult)
const initial = addAndMult(undefined)
/*
* {
* sum: 0,
* totalOperations: 0
* }
*
* First, reducerAdd is called, which gives us initial state { sum: 0 }
* Second, reducerMult is called, which doesn't have payload, so it
* just returns state unchanged.
* That's why there isn't any `product` prop.
*/
const next = addAndMult(initial, 4)
/*
* {
* sum: 4,
* product: 4,
* totalOperations: 2
* }
*
* First, reducerAdd is called, which changes `sum` = 0 + 4 = 4
* Second, reducerMult is called, which changes `product` = 1 * 4 = 4
* Both reducers modify `totalOperations`
*/
const final = addAndMult(next, 4)
/*
* {
* sum: 8,
* product: 16,
* totalOperations: 4
* }
*/
Use cases
combineReducers - each reducer manage own slice of state (e.g. state.todos and state.logging). This is useful when creating a root reducer.
reduceReducers - each reducer manage the same state. This is useful when chaining several reducers which are supposed to operate over the same state (this might happen for example when combining several reducer created using handleAction from redux-actions)
The difference is obvious from the final state shape.
I also don't get what reduce-reducers is trying to solve. The use case described by #Tomáš can be achieved by a simple Reducer. After all, Reducer is just a function that accepts app-state and an action, and returns an object containing the new app-state. For instance, you can do the following instead of using the provided combineReducers by redux:
import combinationReducer from "./combinationReducer";
import endOfPlayReducer from "./endOfPlayReducer";
import feedbackReducer from "./feedbackReducer";
function combineReducers(appState, action) {
return {
combination: combinationReducer(appState, action),
feedbacks: feedbackReducer(appState, action),
endOfPlay: endOfPlayReducer(appState, action)
};
}
And of course here, your reducers are accepting the whole app-state and returning only the slice they are responsible for. Again, it's just a function, you can customise it anyway you like. You can read more about it here
Related
I have the following pipe
pipe(
getProduct(), //step 1
chain((it) => E.right(Object.assign({}, it, { tax: 0.1 }))), //step 2
chain((it) => E.right(Object.assign({}, it, { delivery: 0.15 }))), //step 3
//chain((it) => E.left('ERROR')), //step 4
E.fold( //final step
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(
`ok ${it.count} ${it.pricePerItem} ${it.tax} ${it.delivery}`
)
}
)
)
where
getProduct = () => E.right({ count: 10, pricePerItem: 5 })
Step 1 produces output { count: 10, pricePerItem: 5 }
Step 2 takes the output of step 1 as input and produces output { count: 10, pricePerItem: 5, tax: 0.1 }
Step 3 takes the output of step 2 which is { count: 10, pricePerItem: 5, tax: 0.1 } and produces output { count: 10, pricePerItem: 5, tax: 0.1, delivery: 0.15 }
Step 4 is just a placeholder where it could potentially produces a left to indicate an error condition. I just left it out.
It works as expected in a pipe. But I do NOT want that.
I want step 2 to takes the input { count: 10, pricePerItem: 5 } and add tax to it. Parallelly, I want step 3 to take the same input { count: 10, pricePerItem: 5 } and add delivery to it.
Then I want step 4 to take the output of step 2 and step 3 and merge them back.
I saw something involved bind and/or do notation such as in this answer but is not quite sure.
So how the flow be branched and merged as opposed to always run in a pipe?
Update
The equivalent in imperative programming is as follow:
const products = getProducts()
const productsWithTax = getProductsWithTax(products)
const productsWithDelivery = getProductsWithDelivery(products)
const productsWithTaxAndDelivery = getWithTaxAndDelivery(productsWithTax, productsWithDelivery)
The point is to I do not want the true pipe.
You can definitely use do notation here. Here's a solution that stays true to what you're looking for:
import * as E from 'fp-ts/Either';
import { Either } from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
// Your base product type
interface Product {
count: number
pricePerItem: number
}
// Something that has a tax added
interface Tax {
tax: number
}
// Something that has a delivery added
interface Delivery {
delivery: number
}
declare function getProduct(): Either<Error, Product>
declare function getTax(p: Product): Either<Error, Product & Tax>
declare function getDelivery(p: Product): Either<Error, Product & Delivery>
function solution1(): Either<Error, Product & Tax & Delivery> {
return pipe(
// begin the `do` notation
E.Do,
// (1)
// bind your product to a variable `p`
E.bind('p', getProduct),
// (2)
// use your product to get your product & tax, and bind that to `pTax`
E.bind('pTax', ({ p }) => getTax(p)),
// (3)
// use your product to get your product & delivery, and bind that to `pDelivery`
E.bind('pDelivery', ({ p }) => getDelivery(p)),
// (4)
// merge your original product, product w/tax, and product w/delivery
E.map(({ p, pTax, pDelivery }) => ({ ...p, ...pTax, ...pDelivery }))
);
}
Admittedly, there's some unnecessary overlap of return types here and we have to awkwardly merge the object at the end. Instead of returning extended objects between functions, you could return tax and delivery as standalone results and merge everything at the end:
declare function getProduct(): Either<Error, Product>
declare function getTax(p: Product): Either<Error, number> // changed
declare function getDelivery(p: Product): Either<Error, number> // changed
function solution2(): Either<Error, Product & Tax & Delivery> {
return pipe(
E.Do,
// get product
E.bind('p', getProduct),
// use product to get tax
E.bind('tax', ({ p }) => getTax(p)),
// use product to get delivery
E.bind('delivery', ({ p }) => getDelivery(p)),
// merge tax and delivery into product
E.map(({ p, tax, delivery }) => ({ ...p, tax, delivery })) //
);
}
And note, even though we're using do, there's no escaping pipe. Also, working with Either is going to be synchronous all the way. You won't be able to get concurrency without switching to TaskEither or something similar.
When I implement the MW it works great but how do I invoke the "cancel" function
if I want to clear the timeout?
Here's is the code:(taken from Redux middleware)
/**
* Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds.
* Makes `dispatch` return a function to cancel the timeout in this case.
*/
const timeoutScheduler = store => next => action => {
if (!action.meta || !action.meta.delay) {
return next(action)
}
const timeoutId = setTimeout(
() => next(action),
action.meta.delay
)
return function cancel() {
clearTimeout(timeoutId)
}
}
Assuming all the other middleware in the chain correctly do return next(action), then your call to dispatch() would return this cancel function. For example:
const cancel = store.dispatch({type : "INCREMENT", meta : {delay : 1000}});
// kill it off
cancel();
Or, similarly with a bound action creator in a React component:
// assume we have an action creator like this passed to connect():
function incrementWithDelay() {
return {type : "INCREMENT", meta : {delay : 1000}};
}
const cancel = this.props.incrementWithDelay();
cancel();
import types from "../actions/types";
export default function(state = null, action) {
switch (action.type) {
case types.fetchCartProducts:
return action.payload || false;
case types.modifyCart:
debugger;
switch (action.payload.operation) {
case "subtract":
const index = action.payload.index;
let isSingleCount = state[index] === 1;
let chosenIds = state;
if (isSingleCount) {
chosenIds = chosenIds.filter(index => index != index);
} else {
[
...chosenIds.slice(0, index),
{ ...chosenIds[index], count: chosenIds[index].count - 1 },
...chosenIds.slice(index + 1)
];
}
return (
chosenIds
)
}
default:
return state;
}
}
{
"Products": [
{
index: 1,
name: "Shirt",
price: 1.9,
count: 2
},
{
index: 2,
name: "Jeans",
price: 1.9,
count: 2
}
]
}
I have a react component showing cart products. Each product in the cart is a seperate div and having + and - buttons to increase, decrease the no of that product. On - click I want to decrease the quantity and also if count is reduced to 0 I want to remove this product as well from my redux state.
Now I have my reducer where first I am checking if the count is 1 then removing the product itself else reducing the count only. I am returning the state but its not updating the DOM
Can anyone help in this am I doing something wrong in returning state.
Thanks
It looks like you are directly manipulating the state, which will cause problems in React. Instead of let chosenIds = state;, you should copy the state, let chosenIds = Object.assign({}, state);. Then you can manipulate chosenIds as you wish.
Looks like you forgot to include a assignment statement in the else block.
} else {
chosenIds = [
...chosenIds.slice(0, index),
{ ...chosenIds[index], count: chosenIds[index].count - 1 },
...chosenIds.slice(index + 1)
];
}
Instead of this complicated operation, you could use array.map to update a single item in the array.
chosenIds = state.map(item => item.index === index ? {...item, count: item.count - 1} : item)
I've just normalised the state of an app I'm working on (based on this article) and I'm stuck trying to add/remove items from part of my state tree based on quantity.
Part of my state tree cart is solely responsible for housing the quantity of tickets that are in the cart, organised by ID. When the user changes the quantity, an action is dispatched UPDATE_QTY which has the qty and the id.
The state starts off correct as the incoming data has the qty but I can't seem to figure out the syntax to remove the item from the cart reducer if qty is 0, also how to add it back in if the qty is 1 or more.
Could someone offer advice on the correct syntax to achieve this please?
EDIT: I'm wondering if I'm trying to do too much inside the UPDATE_QTY action and that I should have separate actions for deleting and adding items.
byId reducer
export function byId(state = initialState, action) {
switch (action.type) {
case SET_INITIAL_CART_DATA:
return Object.assign({}, state, action.tickets);
case UPDATE_QTY: // Here, I need to check if action.qty is 0 and if it is I need to remove the item but also add it back in if action.qty > 0
return {
...state,
[action.id]: { ...state[action.id], qty: action.qty }, // Updating the qty here works fine
};
default:
return state;
}
}
Simplfied state tree
const state = {
cart: {
byId: {
'40': { // How can I remove these items when qty is 0 or add back in if > 0?
qty: 0,
id: '40'
},
'90': {
qty: 0,
id: '90'
}
},
allIds: [
[
'40',
'90',
]
]
},
}
I also need the IDs to be reflected in my allIds reducer.
allIds reducer
export function allIds(state = [], action) {
switch (action.type) {
case SET_INITIAL_CART_DATA:
return [...state, ...action.allIds];
case UPDATE_QTY:
return [ONLY IDS WITH QTY]
default:
return state;
}
}
For this I'm not sure if the allIds reducer needs to be connected to the byIds reducer and take information from there. I would love to hear what best practice for something like this would be.
Why have separate reducers for byIds and allIds? I would combine these into one cart reducer and maintain the allIds state with byIds:
case SET_INITIAL_CART_DATA:
// just guessing here...
const { tickets } = action;
const allIds = tickets
.reduce((arr, ticket) => arr.concat(ticket.id), []);
return {
byIds: { ...tickets },
allIds
}
case UPDATE_QTY: {
const { byIds, allIds } = state;
const { id, qty } = action;
const idx = allIds.indexOf(id);
const next = { };
if (qty > 0) {
next.byIds = {
...byIds,
[id]: { id, qty }
};
next.allIds = idx === -1 ? allIds.concat(id) : [ ...allIds ];
return next;
}
next.byIds = { ...byIds };
delete next.byIds[id];
next.allIds = idx === -1 ? [ ...allIds ] : [
...allIds.slice(0, idx),
...allIds.slice(idx + 1)
];
return next;
}
However, what state do you want normalized? If this represents a shopping cart of tickets, the tickets are what would be normalized, and the cart would just represent the quantity of tickets to be purchased. Then your state would look something like this:
{
tickets: {
byIds: {
'1': { id, name, price, ... },
'2': { ... },
'3': { ... },
...
}
allIds: [ '1', '2', '3', ... ]
},
cart: [
{ id: 2, qty: 2 },
{ id: 1, qty: 1 }
]
}
The use of an array for the cart state maintains insertion order.
Sometimes (when you only iterate through ids and get by id) it's enough to remove id from allIds and skip all unnecessary computations.
case actionTypes.DELETE_ITEM: {
const filteredIds = state.allIds.filter(id => id !== action.itemId);
return {
...state,
allIds: filteredIds
};
}
Let's say I have this state:
state: {
field1: value1,
field2: {a: 5, b: 7}
}
If a reducer wants to update only field1, can the reducer return a new object containing a new field1 and the existing object state.field2 as field2 property of the new returned state? Or does the reducer have to clone field2?
Use spread operator
return {
...state,
field1: newVal
}
Here is the link detailed immutable update patterns
http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
Yes. Not only is recycling state permissible, it's recommended.
Say your initial state is:
{
object1: { /* key-pair values */ },
object2: { /* key-pair values */ },
}
If you update your state like this:
// bad
return {
object1: action.object1,
object2: Object.assign({}, state.object2),
}
Then your app thinks object2 has changed even when it hasn't. This may cause unnecessary calculations and re-renders in your React components.
It's much better to only update the parts of your state that have actually changed.
// good
return Object.assign({}, state, {
object1: action.object1,
});
Object.assign() is what you are looking for, now you can also use the spread operator (...) , but be aware that those are ES6 features, so something like internet explorer (even 11) will crash at both , so you will need a polyfill, check this link it will give you more infos about it and also a polyfill for older browsers
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/assign
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { b: 3 , c:2 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 3 , c: 2 }
hope it helps