My application is in react and redux technology.
I created a helper that formats my account bill and money, like this:
export const formatBill = (bill) => ({
...bill,
amountMoney: bill.amountMoney?.toLocaleString(undefined, {
minimumFractionDigits: 2,
}),
accountBillNumber: bill.accountBillNumber
.replace(/(^\d{2}|\d{4})+?/g, '$1 ')
.trim(),
});
Thanks to this, I can do something in the redux reducer and it works great:
...
case GET_BILLS_SUCCESS:
draft.bills = action.bills.map(formatBill);
break;
...
Now I have a problem because I would like to use this function for the transfer history. The problem is that my payload doesn't look so friendly. It's similar to this:
transactions: {
data: [
{
amountMoney: '10.00',
recipientBill: {
uuid: '8b9cef86-3987-4a71-badc-996caca8d8e0',
accountBillNumber: '19282292972339385206612752',
currency: {
name: 'PLN',
},
user: {
uuid: 'c58a9b81-2d16-4b07-804d-0b1fc34381ed',
}
},
senderBill: {
uuid: '6308be25-5bde-4f63-83d1-7bd47a829e61',
accountBillNumber: '76282292974456140174811708',
currency: {
name: 'USD',
},
user: {
uuid: 'af031321-f64b-4e91-948b-0c8c8ac4804c',
}
}
}
],
meta: {
page: 1,
take: 10,
itemCount: 1,
pageCount: 1
}
};
so now I would like to formatBill() the bill in recipientBill.accountBillNumber and senderBill.accountBillNumber How can I save this nicely in the redux reducer? I tried to solve this problem similar to this, but this is not the right solution:
case GET_TRANSACTION_HISTORY_SUCCESS:
draft.transactions.data = [
action.transactions.data.map((transaction) =>
formatBill(transaction.recipientBill),
),
action.transactions.meta,
];
break;
Related
I have to write a javascript function which can validate data according to this kind of schema. How should i go about this? Can i use json schema for this. That does'nt work for fucnctions so how should i solve this? Is there any native js way to go about this?
const userSchema = {
name: {
fn: () => String,
option: { default: 'Abhi', enum: ['John', 'Rick', 'Dan'] }
},
phone: {
fn: () => Number,
option: {}
},
cart: {
fn: () => [
{
id: {
fn: () => String,
option: {}
},
count: {
fn: () => Number,
option: {}
}
}
],
option: {}
},
address: {
contactName: {
fn: () => String,
option: {}
},
detailAddress: {
line1: {
fn: () => String,
option: {}
},
line2: {
fn: () => String,
option: {}
},
line3: {
fn: () => String,
option: {}
}
},
pin: {
fn: () => Number,
option: {}
},
country: {
fn: () => String,
option: {}
}
}
};
const user = {
name: 'Ric',
phone: 6610592314,
address: {
contactName: 'Bitu',
detailAddress: {
line1: 'Colony: 2/A',
line2: 'Q. No.: 3-018',
line3: 'Near IT store'
},
pin: 770017,
country: 'India'
},
cart: [
{
id: 'newId',
count: 2
}
]
};
Joi is a good library for this. Flow and TypeScript also work at compile-time, but won't help you with runtime validation. Joi's function validation isn't perfect, but there's a workaround for argument validation here.
I've been working lately on a JS Runtime Scheme Validation Tool, creatively called Types.
It is still at its early stages (powerful features implementations are expected in the near future), but I think it could fit your needs. Currently it is also able to handle circular schemes.
To install it, do yarn add #codistica/types or npm i #codistica/types.
Then, here you have an usage example (documentation is still work in progress):
import {Types} from '#codistica/types';
const myFunctionSchema = new Types({
argA: {type: '!undefined'},
argB: {type: 'Function'},
argC: {
type: 'Object',
def: {
propA: {type: 'number', min: 0, max: 20, def: 10},
propB: {type: 'boolean', def: false},
propC: {type: ['Function', 'null'], def: null},
propD: {type: 'Array<number>', def: [0, 1, 2]}
}
}
});
function myFunction(argA, argB, argC) {
({argA, argB, argC} = myFunctionSchema.validate({
argA,
argB,
argC
}));
if (!myFunctionSchema.isValid()) {
return;
}
}
export {myFunction};
If you would like to give it a try, I would be glad to assist you in any possible way. The above example is really an underestimation. Maybe you could also give us some useful feedback. Please let me know.
In my angular application i am having the data as follows,
forEachArrayOne = [
{ id: 1, name: "userOne" },
{ id: 2, name: "userTwo" },
{ id: 3, name: "userThree" }
]
forEachArrayTwo = [
{ id: 1, name: "userFour" },
{ id: 2, name: "userFive" },
{ id: 3, name: "userSix" }
]
newObj: any = {};
ngOnInit() {
this.forEachArrayOne.forEach(element => {
this.newObj = { titleOne: "objectOne", dataOne: this.forEachArrayOne };
})
this.forEachArrayTwo.forEach(element => {
this.newObj = { titleTwo: "objectTwo", dataTwo: this.forEachArrayTwo };
})
console.log({ ...this.newObj, ...this.newObj });
}
In my real application, the above is the structure so kindly help me to achieve the expected result in the same way..
The working demo https://stackblitz.com/edit/angular-gyched which has the above structure.
Here console.log(this.newObj) gives the last object,
titleTwo: "ObjectTwo",
dataTwo:
[
{ id: 1, name: "userFour" },
{ id: 2, name: "userFive" },
{ id: 3, name: "userSix" }
]
but i want to combine both and need the result exactly like the below..
{
titleOne: "objectOne",
dataOne:
[
{ id: 1, name: "userOne" },
{ id: 2, name: "userTwo" },
{ id: 3, name: "userThree" }
],
titleTwo: "ObjectTwo",
dataTwo:
[
{ id: 1, name: "userFour" },
{ id: 2, name: "userFive" },
{ id: 3, name: "userSix" }
]
}
Kindly help me to achieve the above result.. If i am wrong in anywhere kindly correct with the working example please..
You're assigning both values to this.newObj, so it just overwrites the first object.
Also, there is no need for your loop. It doesn't add anything.
Instead, you can do:
this.newObjA = { titleOne: "objectOne", dataOne: this.forEachArrayOne };
this.newObjB = { titleTwo: "objectTwo", dataTwo: this.forEachArrayTwo };
console.log({ ...this.newObjA, ...this.newObjB });
**
EDIT **
Having spoken to you regarding your requirements, I can see a different solution.
Before calling componentData, you need to make sure you have the full data. To do this, we can use forkJoin to join the benchmark requests, and the project requests into one Observable. We can then subscribe to that Observable to get the results for both.
The code would look something like this:
createComponent() {
let benchmarks, projects;
let form = this.productBenchMarkingForm[0];
if (form.benchmarking && form.project) {
benchmarks = form.benchmarking.filter(x => x.optionsUrl)
.map(element => this.getOptions(element));
projects = form.project.filter(x => x.optionsUrl)
.map(element => this.getOptions(element));
forkJoin(
forkJoin(benchmarks), // Join all the benchmark requests into 1 Observable
forkJoin(projects) // Join all the project requests into 1 Observable
).subscribe(res => {
this.componentData({ component: NgiProductComponent, inputs: { config: AppConfig, injectData: { action: "add", titleProject: "project", dataProject: this.productBenchMarkingForm[0] } } });
})
}
}
getOptions(element) {
return this.appService.getRest(element.optionsUrl).pipe(
map((res: any) => {
this.dataForOptions = res.data;
element.options = res.data;
return element;
})
)
}
Here is an example in Stackblitz that logs the data to the console
I've been working on a React/Redux application for building a quote. A gross simplification of my state would look something like this:
{
account: { name: 'john doe' },
lineItems:[
{ product: {id: 123, ...}, price: 10, units: 5 },
{ product: {id: 124, ...}, price: 10, units: 5 },
],
modifiers: { couponCode: 'asdf', vip: true }
}
and my reducers would be sliced something like this:
const appReducer = combineReducers<GlobalState>({
account: accountReducer,
lineItems: lineItemReducer,
modifiers: modifersReducer,
});
I've just recently gotten a requirements where I would essentially need to be able to render the entire app multiple times on a single page (basically show 1 or more quotes for different accounts on a single page). So a single state would now need to look something like this:
{
quotes: {
"0": {
account: { name: 'john doe' },
lineItems:[
{ product: {id: 123, ...}, price: 10, units: 5 },
{ product: {id: 124, ...}, price: 10, units: 5 },
],
modifiers: { couponCode: 'asdf', vip: true }
},
"1": {
account: { name: 'billy jean' },
lineItems:[
{ product: {id: 123, ...}, price: 10, units: 5 },
],
modifiers: { couponCode: '', vip: false }
},
}
}
But obviously this new state shape doesn't really work with how I've sliced my reducers. Also, seems like I'd have to refactor all my actions so that I know which quote they should be operating on? For example, if I had an action like this:
{
type: 'UPDATE_PRICE'
payload: { productId: 123, newPrice: 15 }
}
Seems like the product 123 on both quotes would be updated.
Maybe there is instead some way I can just render the entire app on the page without having to refactor my entire state? I'm not sure what my best approach would be that wouldn't requirement me to rewrite large portions of the app.
This should give you the idea. It's basically using one reducer inside another one. As simple as using a function within another function body. You can run it on runkit.com as well.
const { createStore, combineReducers } from 'redux';
const UPDATE_ACCOUNT = 'app/updat-account';
const ADD_QUOTE = 'quote/add-quote';
const appActions = {
updateAcount: (q_id, a) => ({ type: UPDATE_ACCOUNT, payload: { q_id, name: a }}),
};
const quoteActions = {
addQuote: q_id => ({ type: ADD_QUOTE, payload: q_id }),
};
const accountReducer = (app = {}, action) => {
const { type, payload } = action;
switch (type) {
case UPDATE_ACCOUNT:
return { ...app, name: payload.name }
default:
return app;
}
};
const appReducer = combineReducers({
account: accountReducer,
lineItems: (app ={}, action) => app, // just a placeholder
modifiers: (app ={}, action) => app, // just a placeholder
});
const quoteReducer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case ADD_QUOTE:
return { ...state, [payload]: {} };
case UPDATE_ACCOUNT: {
const app = state[payload.q_id];
return app
? { ...state, [payload.q_id]: appReducer(state[payload.q_id], action) }
: state;
}
default:
return state;
}
}
const store = createStore(quoteReducer);
store.dispatch(quoteActions.addQuote(3));
store.dispatch(quoteActions.addQuote(2));
store.dispatch(appActions.updateAcount(3, 'apple'));
store.dispatch(appActions.updateAcount(4, 'orange')); // non-existent quote
store.getState():
/**
{
"2": {},
"3": {
"account": {
"name": "apple"
},
"lineItems": {},
"modifiers": {}
}
}
*/
Just wanted to add my specific answer here..
Basically I added a new root reducer as norbertpy suggested. However, I also had to add a parameter quoteId to each action to specify which quote the action originated from and should operate on. This was the most time consuming part of the refactor as now each component that dispatches actions must have access to the quote key.
Reducer
const quoteReducer = combineReducers({
account: accountReducer,
lineItems: lineItemReducer,
modifiers: modifersReducer,
});
const rootReducer = (state = {quotes: []}, action) => {
const newQuoteState = quoteReducer(state.quotes[action.quoteId], action);
const newQuotes = {...state.quotes};
newQuotes[action.quoteId] = newQuoteState;
return {...state, ...{quotes: newQuotes}};
};
Action
{
type: 'UPDATE_PRICE'
quoteId: '0',
payload: { productId: 123, newPrice: 15 }
}
In Laravel, I have used this approach to combine to collections together and returning as one collection.
$collection = $messages->merge($texts)->sortByDesc('created_at')
If I dd($colection), it shows Collection object all combined and sorted together.
Then I tried to send it to vue via ajax, however, the data is separated again. So my object looks like this:
item: {
messages: [
0: { ... }
1: { ... }
2: { ... }
3: { ... }
],
texts: [
0: { ... }
1: { ... }
]
}
this is because return response()->json('item' => '$collection') separates them as messages and texts again.
I tried combining them like this, but it overwritten the values (I assume because ids are same).
vm item = this;
// in response of ajax get,
.then(function(response) {
var item = response.data.item;
Object.assign(vm.item.messages, vm.item.texts);
});
What is the right way to combine texts into messages and sorting them by timestamps? They all have created_at in the first level of objects like this:
messages: [
0: { created_at: ... }
],
texts: [
0: { created_at: ... }
]
Update: After icepickle's answer, with concat, I was able to combine them in messages array. Now, I have an issue for created_at values as they are converted to strings. Here are some test data. This is what I got after ordering:
messages: [
0: {
msg: 'hello',
created_at: "2017-10-12 00:48:59"
},
1: {
msg: 'mellow',
created_at: "2017-10-11 16:05:01"
},
2: {
msg: 'meow',
created_at: "2017-10-11 15:07:06"
},
4: {
msg: 'test'
created_at: "2017-10-11 17:13:24"
}
5: {
msg: 'latest'
created_at: "2017-10-12 00:49:17"
}
],
Wouldn't it be enough to concat the arrays, and then sort?
A bit like
let result = ([]).concat(item.messages, item.texts);
or in es6
let result = [...item.messages, ...item.texts]
and then calling sort on the result
// in place sort, result will be sorted, and return the sorted array
result.sort((a, b) => a.created_at - b.created_at);
const items = {
messages: [
{
msg: 'hello',
created_at: "2017-10-12 00:48:59"
},
{
msg: 'mellow',
created_at: "2017-10-11 16:05:01"
},
{
msg: 'meow',
created_at: "2017-10-11 15:07:06"
}
],
texts: [
{
msg: 'test',
created_at: "2017-10-11 17:13:24"
},
{
msg: 'latest',
created_at: "2017-10-12 00:49:17"
}
]
};
let result = [...items.messages, ...items.texts].sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
console.log( result );
I have similar data structure to this:
let userInfo = [
{
id: 'id1',
users: [
{
name: 'userName1',
job: 'userJob',
},
{
name: 'userName2',
job: 'userJob',
}
]
},
{
id: 'id2',
users: [
{
name: 'userName3',
job: 'userJob',
},
{
name: 'userName4',
job: 'userJob',
}
]
}
]
Users are
I expected new flattened user stream with RxJS5:
{
parent: id, // parent id, where users[] come from...
name: 'userName'
job: 'userJob'
}
What is the clean functional way to archive this? Thank you...
The easiest approach would just use concatMap() to merge the internal Observable created with Rx.Observable.from with the list of users updated with their parent id.
let userInfo = [
{
id: 'id1',
users: [
{
name: 'userName1',
job: 'userJob',
},
{
name: 'userName2',
job: 'userJob',
}
]
},
{...}
];
let source = Rx.Observable.from(userInfo)
.concatMap(group => {
return Rx.Observable.from(group['users'])
.map(user => {
user['parent'] = group.id;
return user;
});
});
source.subscribe(
res => console.log(res)
);
See live demo: https://jsbin.com/miximas/2/edit?js,console
There're a lot of similar question already:
refactor fat arrow nested rxjs stream
Merge subarrays using Observables
RxJS: JSON data with an array, processing each item further in the stream