Is it good practice to use for loops to sort out data in the same function where it's fetched in Vue? - vue.js

I am using fetch to get some data from an API, I convert this to JSON and want to sort it into different categories. For example tickets (which is what I'm retrieving) with the status active should be in a different array than the ones with status waiting on customer. I want to use a for loop to sort through the results. Should I do this in the same function they're fetched in?
Did a bit of googling but couldn't find a post on this.
methods: {
fetchTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
arrayLength = resJson.length
for(var i = 0; i < arrayLength; i++) {
if(resJson[i]['status'] === 'active') {
//do something
}
else if(resJson[i]['status'] === 'waiting on customer') {
// do something else
}
else {
// do a dance
}
}
});
},
}
So, is it okay to do the above or is it very sensitive to errors/is there a more convenient alternative?

There is a more convient alternative.
You should create two API calls.
1.) /api/activeUsers
2.) /api/waitingCustomers
Then for each API call, you can use the .filter API and return the appropiate array
fetchActiveTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
return resJson.filter(item => {
return item.status ==='active'
})
//do the same for waiting... i.e. resJson(item => {
//return item.status ==='waiting'
//})
}
});
},

I would recommend using .filter() rather than looping over the array to split the source into the pieces you want.
data: {
activeTickets: [],
waitingTickets: []
}
methods: {
fetchTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
this.activeTickets = resJson.filter(function(ticket) { return ticket.status === 'active' });
this.waitingTickets= resJson.filter(function(ticket) { return ticket.status === 'waiting on customer' });
// do things with your filters arrays...
});
},
}

Try
methods: {
async fetchTickets() {
let res = await (await fetch('/api')).json();
let active = res.filter(x=> x['status']=='active');
let waiting = res.filter(x=> x['status']=='waiting on customer');
// ... do something
},
}

Related

issue refer to scope of variable

how to get variable from outer layer method
trying to use a variable in outer layer in my React-Native App
updateCheckBox() {
Constants.TABS.map((item) => {//Constants.TABS is an array
AsyncStorage.getItem(item)//using item as key to fetch from AsyncStorage
.then((res) => {
if(res == 1) {
//debugged here, item was undeined. but i need setState here with item as key. How should i get item here.
this.setState({item: true}) // I need to get the item here, but it show undefined
} else {
this.setState({item:false}) // I need to get the item here, but it show undefined
}
})
})
}
// I need to get the item here, but it show undefined
You need to wrap the item in [] to use it as a key for a property. Like this:
updateCheckBox() {
Constants.TABS.map(item => {
AsyncStorage.getItem(key) //
.then((res) => {
//item is accessible here, to use item as the key to a property wrap it in []
if(res == 1) {
this.setState({[item]: true});
} else {
this.setState({[item]: false});
}
})
})
}
finally, I found there is no issue in this code, the thing is
updateCheckBox() {
Constants.TABS.map((item) => {
let key = item
AsyncStorage.getItem(key)
.then((res) => {
console.log(item, "item is here", res); //item is visible here
console.log(key) //key is all always undefined
if(res == 1) {
this.setState({item: true})
} else {
this.setState({item:false})
}
})
})
}
key is not visible in method then, which I can not explain, but all in all, my code works.

Wait until API fully loads before running next function -- async/await -- will this work?

I am a beginner with Javascript with a bit of knowledge of VueJs. I have an array called tickets. I also have a data api returning two different data objects (tickets and user profiles).
The tickets have user ids and the user profiles has the ids with names.
I needed to create a method that looks at both of that data, loops through it, and assigns the full name of the user to the view.
I was having an issue where my tickets object were not finished loading and it was sometimes causing an error like firstname is undefined. So, i thought I'd try and write an async/await approach to wait until the tickets have fully loaded.
Although my code works, it just doesn't "feel right" and I am not sure how reliable it will be once the application gets larger.
Can I get another set of eyes as to confirmation that my current approach is OK? Thanks!
data() {
return {
isBusy: true,
tickets: [],
userProfiles: [],
}
},
created() {
this.getUserProfiles()
this.getTickets()
},
methods: {
getUserProfiles: function() {
ApiService.getUserProfiles().then(response => {
this.userProfiles = response.data
})
},
getTickets() {
ApiService.getTickets().then(response => {
this.tickets = response.data
this.assignNames(this.tickets)
this.isBusy = false
})
},
// lets wait until the issues are loaded before showing names;
async assignNames() {
let tickets = await this.tickets
var i
for (i = 0; i < this.tickets.length; i++) {
if (tickets[i].assigned_to !== null) {
const result = this.userProfiles.filter(profile => {
return profile.uid == tickets[i].assigned_to
})
tickets[i].assigned_to = result[0].firstname + ' ' + result[0].lastname
}
}
}
}
}
</script>
There are several ways you could do this. Here is the one I prefer without async/await:
created() {
this.load();
},
methods: {
getUserProfiles: function() {
return ApiService.getUserProfiles().then(response => {
this.userProfiles = response.data
})
},
getTickets() {
return ApiService.getTickets().then(response => {
this.tickets = response.data
})
},
load() {
Promise.all([
this.getUserProfiles(),
this.getTickets()
]).then(data => {
this.assignNames();
this.isBusy = false;
});
},
assignNames(){
const tickets = this.tickets;
for (let i = 0; i < this.tickets.length; i++) {
if (tickets[i].assigned_to !== null) {
const result = this.userProfiles.filter(profile => {
return profile.uid == tickets[i].assigned_to
})
tickets[i].assigned_to = result[0].firstname + ' ' + result[0].lastname
}
}
}
}

Using conditional class with store getters: not updating class

I have a div with a conditional class that works well when the app is loaded, but it's not updated when the store data change.
The code in my vue component looks like this
<span class="week-day"
v-bind:class="{ complete: isDayComplete(day) }"
v-for="day in daysInWeek"
v-bind:data-day="day"
> </span>
And I have ...mapGetters(['isDayComplete']) in my computed object.
The getter looks like this
isDayComplete(state) {
return (day) => {
console.log(`called isDayComplete(${day})`)
const formattedDay = moment(day, 'DD/MM/YYYY').format('YYYY-MM-DD');
if (state.daysData[formattedDay]) {
if (state.daysData[formattedDay].meals.length > 0) {
console.log(`day ${day} is complete`);
return true;
} else {
console.log(`day ${day} is NOT complete`);
return false;
}
} else {
console.log(`no data for day ${day}`);
return false;
}
}
},
I update my meals data in a mutation
updateMeals(state, meals) {
_.forEach(meals, (meal) => {
state.daysData[meal.day].meals.push(meal);
});
}
And I have an action that commits that mutation
loadMeals({ state, commit }) {
return new Promise((resolve, reject) => {
get.meals.from.api()
.then((response) => {
commit('initDaysData');
commit('updateMeals', response.data.data);
return resolve();
})
.catch(reject);
});
}
So whenever I call loadMeals the class is not updated if one day changes its status (complete/not-complete). If I reload the page, the class is set correctly.
What am I doing wrong? Thanks!
It's a common reactivity problem. You can make deep copy (use JSON.parse(JSON.stringify())) to make data reactive:
updateMeals(state, meals) {
_.forEach(meals, (meal) => {
state.daysData[meal.day].meals.push(meal);
});
state.daysData = JSON.parse(JSON.stringify(state.daysData))
}
#ittus answer was correct. I found another way to achieve this that maybe could
help someone else.
add another mutation on the store
updateCompletedDays(state) {
const newState = [];
_.forEach(state.daysData, (currentDayData, currentDay) => {
if (currentDayData.meals.length > 0) {
newState.push(currentDay);
}
});
state.completedDays = newState;
},
commit this mutation after meals are updated
change isDayComplete getter to
isDayComplete(state) {
const formattedDay = moment(day, 'DD/MM/YYYY').format('YYYY-MM-DD');
return state.completedDays.indexOf(formattedDay) !== -1;
}
Basically when using reactivity going deep into arrays/object will not work, better have arrays of aggregated data (check also Vue.set api)

Filtering normalized data structure

Forgive me, I'm new to normalizr+redux. I've managed to normalize my data and create a reducer and end up with :
state = {
installations:{
"1":{...},
"2":{...}
}
}
I would then like to filter this data for use in a UI component into two separate categories (in this case where the installation.operator is equal to the current user). I've managed an implementation that works however it seems exhaustive:
const mapStateToProps = (state, ownProps) => {
console.log("mapStateToProps", state.installations);
let assignedInstallations = Object.keys(state.installations)
.filter(i => {
return state.installations[i].operator == state.login;
})
.map(i => {
return state.installations[i];
});
let unassignedInstallations = Object.keys(state.installations)
.filter(i => {
return state.installations[i].operator != state.login;
})
.map(i => {
return state.installations[i];
});
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
I'm also new to ES6 and am not across all the new syntax shortcuts etc so I suspect there are much better ways to do this.
Is there a more succinct approach with a similar outcome?
you can do this with only one reduce():
const mapStateToProps = (state, ownProps) => {
console.log("mapStateToProps", state.installations);
let {assignedInstallations,
unassignedInstallations } = Object.keys(state.installations)
.reduce(function(acc, cur, i){
if(state.installations[i].operator == state.login){
acc.assignedInstallations.push(state.installations[i]);
}else{
acc.unassignedInstallations .push(state.installations[i]);
}
return acc
}, {assignedInstallations: [], unassignedInstallations: [] })
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
lodash (An utility library) have a notion of collection (Here is an example https://lodash.com/docs/4.17.4#filter for filter function). It takes as input Object or Array and returns an Array. It seems to fit to your needs. Here is the refactored code:
import {
filter,
} from 'lodash'
const mapStateToProps = (state, ownProps) => {
let assignedInstallations = filter(state.installations, installation => installation.operator == state.login);
let unassignedInstallations = filter(state.installations, installation => installation.operator != state.login);
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};

How to call a post method from api in a loop Angular

I am trying to do a put request to an api from array that I have. The post wants an object, and I have an array of objects. What I do is a loop itereting the length of my array of objects calling the method into my service. The problem is that just works the first one and the rest are not working. Do I should something like return promise and then call it recursively?
Here I let my method to call the api:
onUpdate() {
for (var i = 0; i < this.conditionsToUpdate.length; i++) {
this.ruleService.updateConditionsFromRule(this.rule.id, this.conditionsToUpdate[i])
.then(_ => {
this.notificationService.addToast('Condition Updated!', '', 2)
})
.catch(err => this.notificationService.handleError("Could not update the
condition!"))
}
}
Finally, on my Service I have my request:
updateConditionsFromRule(idRule: number, condition: ConditionUpdate):Promise<any> {
return this.http.post(`${this.organizationId}/rules/${idRule}/conditions`, condition)
.toPromise()
.then(res => {
const response = <{ id: String, error: IError[] }>res.json();
if (!!response && !!response.error) {
return Promise.reject(response.error)
} else {
return Promise.resolve(response)
}
}).catch(err => Promise.reject(err));
}
And as I said, it just returns me the first post we do, the rest are not being created.
Thank you a lot!
You can use Observable for this, promises will be too limited.
given your array updateConditionsFromRule, this is how to implement such a thing:
let requests:Observable<Response>[] = [];
updateConditionsFromRule.forEach( updateCondition => {
requests.push(this.http.post(`${this.organizationId}/rules/${idRule}/conditions`, condition));
});
// After our loop, requests is an array of Observables, not triggered at the moment.
//Now we use combineLatest to convert our Observable<Response>[] to a Observable<Response[]>.
//This means that the promise will resolve once the last request of the array has finished.
Observable.combineLatest(requests).toPromise()
.then(res => {
const response = <{ id: String, error: IError[] }>res.json();
if (!!response && !!response.error) {
return Promise.reject(response.error)
} else {
return Promise.resolve(response)
}
}).catch(err => Promise.reject(err));
}