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.
Related
I am fetching news data from an API, in the app I need to show 3 lists. today news, yesterday news, article news.
I think I should use redux reselect. However, all the examples I am visiting has a dynamic filter value (state filter) while I need data to be fileted statically (no state changes these filters)
my state at the moment is
{news : [] }
How can I generate something like below using reselect
{news: [], todayNews:[], yesterdayNews:[], articleNews: []}
should I use reselect or I should just filter inside a component? I think reselect is memorized so I prefer to use reselect for performance
You can do something like the following:
const { createSelector } = Reselect;
const state = {
news: [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' },
],
};
const selectNews = (state) => state.news;
const selectOdds = createSelector(selectNews, (news) =>
news.filter(({ id }) => id % 2 !== 0)
);
const selectEvens = createSelector(selectNews, (news) =>
news.filter(({ id }) => id % 2 === 0)
);
const selectFilteredNews = createSelector(
selectNews,
selectEvens,
selectOdds,
(news, even, odd) => ({ news, even, odd })
);
const news = selectFilteredNews(state);
console.log('news:', JSON.stringify(news, undefined, 2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
You use selectors when you need to calculate values based on state such as the total of a list or filtered things from a list. This way you don't need to duplicate the data in your state.
I need to handle a situation where I have 3 endpoints to call and would like to get the data in the most convenient/efficient way. The first call can be handled independently and returns a single result. The second endpoint returns a collection but will need to initiate 0-* subsequent calls, where a given key is present.
Ideally would like to receive the collection (from the 2nd endpoint call) as a mutated/new collection that includes the result from the 3rd endpoint call.
I am currently using forkJoin(observableA$, observableB$) to handle the first 2 calls in parallel but I cannot work out how to include the sequential calls and have the data included in observableB$
//Customer observable
const customer$ = this._customerManagementService.getCustomer(
accountNumber
);
return forkJoin({
customer: customer$,
saleCycles: saleCyclesWithVehicle$
}).pipe(finalize(() => this._loaderFactoryService.hide()));
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
concatMap((results: ISaleCycle[]) => {
return results.map(cycle => {
return this._purchaseVehicleService.getPurchaseVehicle(
cycle.vehicleKey
);
});
})
);
}
I expect the collection to include further data as a new property on the original collection
UPDATE
After a bit more thought maybe I should be using reduce somewhere in the solution. This way I can be in control of what's getting push into the array and it could be dynamic?
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
switchMap((results: ISaleCycle[]) => {
return results.map(cycle => {
if (cycle.vehicleKey) {
return this._purchaseVehicleService
.getPurchaseVehicle(cycle.vehicleKey)
.pipe(
reduce((acc, vehicle) => {
return { cycle: cycle, vehicle: vehicle };
}, []),
toArray()
);
}
else {
///No extra data to be had
}
});
}),
concatAll()
);
}
I would use concatMap() to merge the responses of HTTP requests 2 and 3.
import { of } from 'rxjs';
import { map, concatMap } from 'rxjs/operators';
const pretendGetCustomer = of({accountNumber: 123, name:"John Doe"});
const pretendGetVehiculeHttpRequest = (customerNumber) => {
return of([{custNum: 123, vehicleId:"2"}, {custNum: 123, vehicleId:"1"}]);
}
const pretendGetCyclesHttpRequest = (cycleIds) => {
return of([{id:"1", name:"yellow bike", retailPrice:"$10"}, {id:"2", name:"red bike", retailPrice:"$20"}]);
}
const yourFunction = () => {
pretendGetCustomer.subscribe(customer => {
// Assuming you do other things here with cust, reason why we are subscribing to this separately
// isHappy(customer)
// Your second & third calls
pretendGetVehiculeHttpRequest(customer.accountNumber).pipe(
// Need to use concatMap() to subscribe to new stream
// Note: use mergeMap() if you don't need the 1st stream to be completed
// before calling the rest
concatMap(purchases => {
const cyclesIds = purchases.map(p => p.vehicleId);
// concatMap() requires an Observable in return
return pretendGetCyclesHttpRequest(cyclesIds).pipe(
// Use map() here because we just need to use the data,
// don't need to subscribe to another stream
map(cycles=>{
// Retrun whatever object you need in your subscription
return {
customerNumber: customer.accountNumber,
customerName: customer.name,
purchases: purchases.map(p => cycles.find(c => p.vehicleId === c.id))
}
})
);
})
).subscribe(resultof2and3 => {
// Do something with the new/mutated Object which is a result of
// your HTTP calls #2 and #3
console.log(resultof2and3);
});
});
}
yourFunction();
I made a stackblitz if you want to see the above run (see console): https://stackblitz.com/edit/rxjs-nqi7f1
This is the solution I eventually came up with. I've taken the advice from BoDeX and used concatMap(). In my mind it was clear that I wanted to use forkJoin and be able to reference the results by object key, I.e customer or saleCycles.
In the scenario where a vehicleKey was present I needed to return the results in a defined data structure, using map(). Likewise, if no vehicle was found then I just needed the outer observable.
const customer$ = this._customerManagementService.getCustomer(accountNumber);
const saleCyclesWithVehicle$ = this.getSalesWithVehicle(accountNumber,dealerKey);
getSalesWithVehicle(accountNumber: string, dealerKey: string) {
return this._salesCycleService
.getCyclesForCustomer({
customerNumber: accountNumber,
dealerKey: dealerKey
})
.pipe(
concatMap(cycles => {
return from(cycles).pipe(
concatMap((cycle: ISaleCycle) => {
if (cycle.vehicleKey) {
return this._purchaseVehicleService
.getPurchaseVehicle(cycle.vehicleKey)
.pipe(
map(vehicle => {
return { cycle: cycle, vehicle: vehicle };
})
);
} else {
return of({ cycle: cycle });
}
}),
toArray()
);
})
);
}
return forkJoin({
customer: customer$,
saleCycles: saleCyclesWithVehicle$
}).pipe(finalize(() => this._loaderFactoryService.hide()));
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
};
}
I'm struggling to find a solution to this.
I have the following model, loaded via a promise thru a service.
export class Something {
id: number;
someTime: string;
items: [
{
id: number;
description: string;
unit: string;
portion: number;
std: number;
}
]
}
It has mock data:
const SOMETHINGS: Something[] = [
{
id: 0,
someTime: 'Now',
items: [
{
id: 0,
description: 'A',
unit: 'ml',
portion: 275,
std: 64
},
{
id: 1,
description: 'B',
unit: 'g',
portion: 50,
std: 378
},
....
]
}
]
The actual values are irrelevant, but I need to access each portion and std values. Multiply them and finally SUM them together.
Like so:
275 * 64 = 17600
50 * 378 = 18900
total = 36500
Inside my component I have assigned the returned data to a local array like so.
something: Something[] = [];
constructor( private somethingService: SomethingService ) {}
ngOnInit(){
this.getSomething();
}
getSomething(){
this.somethingService.getSomething().then(something => this.something = something);
}
This value is not inside a repeated section of my template so I can't use an *ngFor type approach.
It would seem logical to use something like:
calcTotal(){
let totals:array<Number>;
let gross:number = 0;
this.something.forEach((items) => total.push( items.portion * items.std ));
totals.forEach((total:number) => gross += total));
return {
total: totals;
}
}
But this doesn't work..
I simply don't know how to access each item and extract the two values, multiply them, save them for later extract the next pair and so on. Then finally take all the results and sum them up and pass it to the view.
Any help would be appreciated.
Steve
you could do this when you get your data and subscribe to it from your service.
this.somethingService.getSomething()
.then((data) => {
this.data = data;
this.totals = this.calculateValues(data);
});
// With the model you have up there it would look like this.
calculateValues(data){
let total = 0;
data.map( somethingObj => {
somethingObj.items.map(itemObj => {
total += (itemObj.portion * itemObj.std);
})
})
return total;
}