Redux, Fetch and where to use .map - json

Consider this scenario:
app loads => fetches json from api => needs to modify json returned
In this case, I'm using moment to make some date modifications and do some grouping that I'll use in the UI. I looked on stack and found a similar question but didn't feel like it provided the clarity I am seeking.
Where should I use .map to create the new objects that contain the formatted & grouped dates? Should I manipulate the raw json in the api call or in the redux action before I dispatch? What is the best practice?
Is it OK to add properties and mutate the object as I am showing below,
service["mStartDate"] = mStartDate before I put the data into my store and treat it as immutable state?
First Approach - changing raw json in the api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
})
}
}
2nd Approach, make a simple api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
}
}
Change the json in the action before dispatching
export function getDataByID() {
return (dispatch, getState) => {
dispatch(dataLookupRequest())
const state = getState()
const groupedData = {}
return TicketRepository.getDataByID(userData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
return groupedData
})
.then(groupedData => {
dispatch(lookupSuccess(groupedData))
})
.catch(err => dispatch(dataLookupFailure(err.code, err.message)))
}
}

All data manipulation should be handled by your reducer. That is, the returned response data should be passed on to a reducer. This practice is common, because this way if there's a problem with your data, you will always know where to look - reducer. So neither of your approaches is "correct". Actions should just take some input and dispatch an object (no data manipulation).
When you want to manipulate data for 'view' purposes only, consider using reselect library, which makes it easier to handle "data views" that are composed of the existing data.

Related

Write key and value to JSON file based on the data provided using Cypress

I have to write the captured data from the application in JSON file as like below:
let expectedKey = 'PaperCode';
cy.get('app-screen').find('#code-details').invoke('val').as(code);
cy.get('#code').then(code) => {
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
if(expectedKey === 'StudentCode'){
appDetails.StudentCode = code;
}
if(expectedKey === 'DepartmentCode'){
appDetails.DepartmentCode = code;
}
if(expectedKey === 'PaperCode'){
appDetails.PaperCode = code;
}
if(expectedKey === 'ResultsCode'){
appDetails.ResultsCode = code;
}
})
})
Here, the key and its value are added to json in multiple if blocks. Still, there are many if blocks to implement based on different codes. I want to remove the if blocks and need to add the key and its value to json file based on the expectedKey. Any help please?
Using bracket notation appDetails[expectedKey] to define the new property,
let expectedKey = 'studentCode';
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
cy.get('app-screen').find('#code-details').invoke('val')
.then(code) => {
appDetails[expectedKey] = code; // add (or overwrite) new key
cy.writeFile('cypress/fixtures/applicationDetails.json', appDetails)
})
})
Clearing the file of all keys except the one you want
const expectedKey = 'studentCode';
const appDetails = {}
cy.get('app-screen').find('#code-details').invoke('val')
.then(code) => {
appDetails[expectedKey] = code; // add (or overwrite) new key
cy.writeFile('cypress/fixtures/applicationDetails.json', appDetails)
})

Object from Observable then Array from Observable inside a foreach. how to order it?Asynchronous Angular 4/5

Here is my problem.
I'm running a method that sends me a json (method = myTableService.getAllTables ()), to create an object (object = this.myTables).
Then I execute the method for each, for each element of this.myTables I execute a new request (request = this.myTableService.getTableStatut (element.theId)).
I retrieve data from a new json to create an object (object = myTableModel).
Each result will be added to this.myTableListProvisory.
The problem is the order of execution.
It execute the console.log before the end of the for each...
This.myTableListProvisory.length and this.myTableList.length return 0.
How to wait for the end of the for each run before running the console.log?
Thank you
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
console.log("this.myTableList.length : " + this.myTableList.length);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
console.log("this.myTableListProvisoire.length : " + this.myTableListProvisoire.length);
})
return this.myTableListProvisoire;
}
Result of console.log
this.myTableListProvisoire.length : 0
this.myTableList.length : 0
UPDATE
I have simplified the code ... I put it in its entirety for the understanding. What I need is to sort the array after it is done. The problem is that I don't know how to use a flatMap method in a query inside a foreach ... I have temporarily placed the sort method inside the subscribe which is a bad solution for the performance. That's why I want to do my sort after the creation of the array. Thank you
export class MyTableComponent implements OnInit {
myTables: any;
statut: any;
myTableModel: MyTableModel;
myTableList: Array<MyTableModel>;
myTableListProvisoire: Array<MyTableModel>;
i: number;
j: number;
myTableModelProvisoire: MyTableModel = null;
constructor(public myTableService: MyTableService) { }
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
for (this.j = 0; this.j < this.myTableListProvisoire.length; this.j++) {
for (this.i = 0; this.i < this.myTableListProvisoire.length - 1; this.i++) {
if (this.myTableListProvisoire[this.i].getTableNumber() > this.myTableListProvisoire[(this.i + 1)].getTableNumber()) {
this.myTableModelProvisoire = this.myTableListProvisoire[this.i];
this.myTableListProvisoire[this.i] = this.myTableListProvisoire[(this.i + 1)];
this.myTableListProvisoire[(this.i + 1)] = this.myTableModelProvisoire;
}
}
}
}, err => {
console.log(err);
})
}, err => {
console.log(err);
})
return this.myTableListProvisoire;
}
}
Well Observables are asynchronous actions and will be executed after finishing the current execution block. So when the js engine comes to your
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
it will only create a subscription, but the code inside of it will be executed after all the other code in the block. So that's why your console.log is being executed before you get any data. So you need to place it inside the .subscribe block to see the. I think there can be a better solution to get the data, but I don't know the structure of the app, so I can't advice. If you create an example on https://stackblitz.com/ I could probably help you out with a better solution.

Too tidious hooks when querying in REST. Any ideas?

I've just started using feathers to build REST server. I need your help for querying tips. Document says
When used via REST URLs all query values are strings. Depending on the service the values in params.query might have to be converted to the right type in a before hook. (https://docs.feathersjs.com/api/databases/querying.html)
, which puzzles me. find({query: {value: 1} }) does mean value === "1" not value === 1 ? Here is example client side code which puzzles me:
const feathers = require('#feathersjs/feathers')
const fetch = require('node-fetch')
const restCli = require('#feathersjs/rest-client')
const rest = restCli('http://localhost:8888')
const app = feathers().configure(rest.fetch(fetch))
async function main () {
const Items = app.service('myitems')
await Items.create( {name:'one', value:1} )
//works fine. returns [ { name: 'one', value: 1, id: 0 } ]
console.log(await Items.find({query:{ name:"one" }}))
//wow! no data returned. []
console.log(await Items.find({query:{ value:1 }})) // []
}
main()
Server side code is here:
const express = require('#feathersjs/express')
const feathers = require('#feathersjs/feathers')
const memory = require('feathers-memory')
const app = express(feathers())
.configure(express.rest())
.use(express.json())
.use(express.errorHandler())
.use('myitems', memory())
app.listen(8888)
.on('listening',()=>console.log('listen on 8888'))
I've made hooks, which works all fine but it is too tidious and I think I missed something. Any ideas?
Hook code:
app.service('myitems').hooks({
before: { find: async (context) => {
const value = context.params.query.value
if (value) context.params.query.value = parseInt(value)
return context
}
}
})
This behaviour depends on the database and ORM you are using. Some that have a schema (like feathers-mongoose, feathers-sequelize and feathers-knex), will convert values like that automatically.
Feathers itself does not know about your data format and most adapters (like the feathers-memory you are using here) do a strict comparison so they will have to be converted. The usual way to deal with this is to create some reusable hooks (instead of one for each field) like this:
const queryToNumber = (...fields) => {
return context => {
const { params: { query = {} } } = context;
fields.forEach(field => {
const value = query[field];
if(value) {
query[field] = parseInt(value, 10)
}
});
}
}
app.service('myitems').hooks({
before: {
find: [
queryToNumber('age', 'value')
]
}
});
Or using something like JSON schema e.g. through the validateSchema common hook.

Angular 2 - Getting object id from array and displaying data

I currently have a service that gets an array of json objects from a json file which displays a list of leads. Each lead has an id and when a lead within this list is clicked it takes the user to a view that has this id in the url ie ( /lead/156af71250a941ccbdd65f73e5af2e67 )
I've been trying to get this object by id through my leads service but just cant get it working. Where am I going wrong?
Also, i'm using two way binding in my html.
SERVICE
leads;
constructor(private http: HttpClient) { }
getAllLeads() {
return this.http.get('../../assets/leads.json').map((response) => response);
}
getLead(id: any) {
const leads = this.getAllLeads();
const lead = this.leads.find(order => order.id === id);
return lead;
}
COMPONENT
lead = {};
constructor(
private leadService: LeadService,
private route: ActivatedRoute) {
const id = this.route.snapshot.paramMap.get('id');
if (id) { this.leadService.getLead(id).take(1).subscribe(lead => this.lead = lead); }
}
JSON
[
{
"LeadId": "156af71250a941ccbdd65f73e5af2e66",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Fred Dibnah",
"LeadNumber": "1603041053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e67",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Harry Dibnah",
"LeadNumber": "1603021053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e68",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "John Doe",
"LeadNumber": "1603021053",
}
]
You didn't used the newly created leads array (const leads is not this.leads), so do this:
getLead(id: any) {
return this.getAllLeads().find(order => order.LeadId === id);
}
And change your map to flatMap, because from the server you get an array, but you have to transform it to a stream of its items:
getAllLeads() {
return this.http.get('../../assets/leads.json').flatMap(data => data);
}
Don't forget to import it if you have to: import 'rxjs/add/operator/flatMap';
You can have getLead in your component level itself since you are not making any api to get the information. In your component,
this.lead = this.leads.find(order => order.id === id);
or to make the above service work, just do leads instead of this.leads
const lead = leads.find(order => order.id === id);

How to alter keys in immutable map?

I've a data structure like this (generated by normalizr):
const data = fromJS({
templates: {
"83E51B08-5F55-4FA2-A2A0-99744AE7AAD3":
{"uuid": "83E51B08-5F55-4FA2-A2A0-99744AE7AAD3", test: "bla"},
"F16FB07B-EF7C-440C-9C21-F331FCA93439":
{"uuid": "F16FB07B-EF7C-440C-9C21-F331FCA93439", test: "bla"}
}
})
Now I try to figure out how to replace the UUIDs in both the key and the value of the template entries. Basically how can I archive the following output:
const data = fromJS({
templates: {
"DBB0B4B0-565A-4066-88D3-3284803E0FD2":
{"uuid": "DBB0B4B0-565A-4066-88D3-3284803E0FD2", test: "bla"},
"D44FA349-048E-4006-A545-DBF49B1FA5AF":
{"uuid": "D44FA349-048E-4006-A545-DBF49B1FA5AF", test: "bla"}
}
})
A good candidate seems to me the .mapEntries() method, but I'm struggling on how to use it ...
// this don't work ... :-(
const result = data.mapEntries((k, v) => {
const newUUID = uuid.v4()
return (newUUID, v.set('uuid', newUUID))
})
Maybe someone can give me a hand here?
mapEntries is the correct method. From the documentation, the mapping function has the following signature:
mapper: (entry: [K, V], index: number, iter: this) => [KM, VM]
This means that the first argument is the entry passed in as an array of [key, value]. Similarly, the return value of the mapper function should be an array of the new key and the new value. So your mapper function needs to look like this:
([k, v]) => {
const newUUID = uuid.v4()
return [newUUID, v.set('uuid', newUUID)]
}
This is equivalent to the following (more explicit) function:
(entry) => {
const key = entry[0]; // note that key isn't actually used, so this isn't necessary
const value = entry[1];
const newUUID = uuid.v4()
return [newUUID, value.set('uuid', newUUID)]
}
One thing to note is that the templates are nested under the templates property, so you can't map data directly -- instead you'll want to use the update function.
data.update('templates', templates => template.mapEntries(...)))
So putting everything together, your solution should look like the following:
const result = data.update('templates', templates =>
templates.mapEntries(([k, v]) => {
const newUUID = uuid.v4()
return [newUUID, v.set('uuid', newUUID)]
})
);