I have an array of objects that I get from an api, I get the data but I want to remove the ones that have a finish status after x time.
First I must show all the records, after a certain time the records with FINISH status must be deleted
I am using vue.
This is the response I get:
[
{
"id": "289976",
"status": "FINISH"
},
{
"id": "302635",
"status": "PROGRESS"
},
{
"id": "33232",
"status": "PROGRESS"
}
]
This is the method that obtains the information:
I use setTimeout to be able to delete the records with FINISH status after a certain time
getTurns() {
fetch('ENPOINT', {
method: 'POST',
body: JSON.stringify({id: this.selected}),
headers: {
'Content-Type': 'application/json'
}
}).then(response => response.json())
.then(data => {
this.turns = data;
data.forEach(turn => {
if(turn.status == 'FINISH'){
setTimeout(() => {
this.turns = data.filter(turn => turn.status !== 'FINISH');
}, 6000);
}
});
})
.catch(error => console.error(error));
}
I have tried going through the array and making a conditional and it works for me, but when I call the method again I get the records with FINISH status again. I need to call the method every time since the data is updated
mounted () {
this.getTurns();
setInterval(() => {
this.getTurns();
}, 5000);
}
maybe I need to order in another way, or that another javascript method I can use
filter is exactly what you need. I don't get why you wrap everything in setInterval and wait for 5 or 6 seconds.
Why don't you return the filtered data instead?
return data.filter(turn -> turn.status !== 'FINISHED');
You mistake in this place
this.turns = data;
It put data in component property turns before filter;
Do it after filter:
.then(data => {
// get before filter
this.turns = data;
// filter data after 6 sec
setTimeout(() => {
data.forEach(turn => {
this.turns = data.filter(turn => turn.status !== 'FINISH');
});
}, 6000)
})
Sorry, but I don't understand why you use setTimeout inside fetch. Do you sure that it necessary?
Code that you need on CodeSandBox. It sure works.
https://codesandbox.io/s/vue-getdata-and-filter-it-after-delay-6yrj16?file=/src/components/HelloWorld.vue
Use filter for your case: turn => turn.status !== 'FINISH'
You can avoid the setTimeout() delay if you take the promise as what it is: a promise that some data will be there!
The following snippet will provide the data in the global variable turns as soon as it has been received from the remote data source (in this example just a sandbox server). The data is then filtered to exclude any entry where the property .company.catchphrase includes the word "zero" and placed into the global variabe turns. The callback in the .then()after the function getTurns() (which returns a promise!) will only be fired once the promise has been resolved.
var turns; // global variable
function getTurns() {
return fetch("https://jsonplaceholder.typicode.com/users")
.then(r => r.json()).then(data =>
turns=data.filter(turn=>!turn.company.catchPhrase.includes("zero"))
)
.catch(error => console.error(error));
}
getTurns().then(console.log);
Related
I'm trying to add a new object to the end of an array that I'm dynamically fetching over my API. Users complete a form so the data is passed from the form to the state.
The initial first fetch is storing the original array to some react state which is fine, then at some point, a single object should be added to the end of the original array, so the whole array will contain the original data, plus the new object added.
Naturally, I'm trying to use array.push() to try and achieve this, but I keep getting the index rather than the data object.
// declare state will be an array
const [originalPages, setOriginalPages] = useState([]);
// Get all the existing data
loadInitialValues() {
return fetch(`example.com/api/collections/get/testing_features?token=${process.env.REACT_APP_API_KEY}`)
.then((res) => {
return res.json();
})
.then((res)=>{
// set state to be the whole array for this post
setOriginalPages(res.entries[4].pagebuild);
return res.entries[4].pagebuild[0].settings;
})
.catch((err)=>{
console.error(err);
})
}
At this point the data is all working completely fine, the collection of the new data from the forms works fine too. However, this is the point when the data goes to get posted back to update the API but just returns a number:
onSubmit(formData) {
// Dynamically hook all the newly collected form data to this new data object
let theData = {
component: 'page',
settings: {
title: formData.title,
pageOptions: {
pageSideImg: formData.pageOptions.pageSideImg,
isReversed: formData.pageOptions.isReversed,
paraGap: formData.pageOptions.paraGap,
paraFont: formData.pageOptions.paraFont,
},
pageNavigation: {
pageSlug: formData.pageNavigation.pageSlug,
nextPage: formData.pageNavigation.nextPage,
prevPage: formData.pageNavigation.prevPage,
},
globalOptions: {
projectName: formData.globalOptions.projectName,
transType: formData.globalOptions.transType,
menuTrans: formData.globalOptions.menuTrans,
},
blocks: formData.blocks
}
};
cms.alerts.info('Saving Content...');
return fetch(`example.com/api/collections/save/testing_features?token=${process.env.REACT_APP_API_KEY}`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: {
// Only returning the array count as a number
pagebuild: originalPages.push(theData),
_id: "610963c235316625d1000023"
}
})
})
.then(response => response.json())
.catch((err) => {
cms.alerts.error('Error Saving Content');
console.log(err);
});
},
If anyone has any ideas as to why this is happening id greatly appreciate it!
For reference, I've checked here and maybe I should use spread instead?
The Array.push doesn't return the final array you would need, but the final length of modified array (that's why you thought it was an index).
Replace this string pagebuild: originalPages.push(theData), with this one:
pagebuild: [...originalPages, theData],
Of course, if you want to update the internal originalPages state value, call this within your onSubmit():
setOriginalPages(x => [...x, theData]);
Via a microservice, I retrieve several packages of JSON data and spit them out onto a Vue.js-driven page. The data looks something like this:
{"data":{"getcompanies":
[
{"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"},
{"id":7,"name":"McMillan","address":null,"zip":"15090"},
{"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}
]
}}
{"data":{"getusers":
[{"id":22,"name":"Fred","address":"Parmesean Street","zip":"15090"},
{"id":24,"name":"George","address":"Loopy Lane","zip":"15090"},
{"id":25,"name":"Lucy","address":"Farm Road","zip":"15090"}]}}
{"data":{"getdevices":
[{"id":2,"name":"device type 1"},
{"id":4,"name":"device type 2"},
{"id":5,"name":"device type 3"}]}}
...and I successfully grab them individually via code like this:
getCompanies() {
this.sendMicroServiceRequest({
method: 'GET',
url: `api/authenticated/function/getcompanies`
})
.then((response) => {
if(response.data) {
this.dataCompanies = response.data.getcompanies
} else {
console.error(response)
}
}).catch(console.error)
}
...with getUsers() and getDevices() looking respectively the same. getCompanies() returns:
[{"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"},
{"id":7,"name":"McMillan","address":null,"zip":"15090"},
{"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}]
...which I relay to the Vue template in a table, and this works just fine and dandy.
But this is obviously going to get unwieldy if I need to add more microservice calls down the road.
What I'm looking for is an elegant way to jump past the response.data.*whatever* and get to those id-records with a re-useable call, but I'm having trouble getting there. response.data[0] doesn't work, and mapping down to the stuff I need either comes back undefined, or in bits of array. And filtering for response.data[0].id to return just the rows with ids keeps coming back undefined.
My last attempt (see below) to access the data does work, but looks like it comes back as individual array elements. I'd rather not - if possible - rebuild an array into a JSON structure. I keep thinking I should be able to just step past the next level regardless of what it's called, and grab whatever is there in one chunk, as if I read response.data.getcompanies directly, but not caring what 'getcompanies' is, or needing to reference it by name:
// the call
this.dataCompanies = this.getFullData('companies')
getFullData(who) {
this.sendMicroServiceRequest({
method: 'GET',
url: 'api/authenticated/function/get' + who,
})
.then((response) => {
if(response) {
// attempt 1 to get chunk below 'getcompanies'
Object.keys(response.data).forEach(function(prop) {
console.log(response.data[prop])
})
// attempt 2
// for (const prop in response.data) {
// console.log(response.data[prop])
// }
let output = response.data[prop] // erroneously thinking this is in one object
return output
} else {
console.error(response)
}
}).catch(console.error)
}
...outputs:
(63) [{…}, {…}, {…}] <-- *there are 63 of these records, I'm just showing the first few...*
0: {"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"}
1: {"id":7,"name":"McMillan","address":null,"zip":"15090"},
2: {"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}...
Oh, and the return above comes back 'undefined' for some reason that eludes me at 3AM. >.<
It's one of those things where I think I am close, but not quite. Any tips, hints, or pokes in the right direction are greatly appreciated.
I feel it's better to be explicit about accessing the object. Seems like the object key is consistent with the name of the microservice function? If so:
getData(functionName) {
return this.sendMicroServiceRequest({
method: 'GET',
url: "api/authenticated/function/" + functionName
})
.then( response => response.data[functionName] )
}
getCompanies(){
this.getData("getcompanies").then(companies => {
this.dataCompanies = companies
})
}
let arrResponse = {data: ['x']};
let objResponse = {data: {getcompanies: 'x'}};
console.log(arrResponse.data[0]);
console.log(Object.values(objResponse.data)[0]);
response.data[0] would work if data was an array. To get the first-and-only element of an object, use Object.values(response.data)[0] instead. Object.values converts an object to an array of its values.
Its counterparts Object.keys and Object.entries likewise return arrays of keys and key-value tuples respectively.
Note, order isn't guaranteed in objects, so this is only predictable in your situation because data has exactly a single key & value. Otherwise, you'd have to iterate the entry tuples and search for the desired entry.
firstValue
Let's begin with a generic function, firstValue. It will get the first value of an object, if present, otherwise it will throw an error -
const x = { something: "foo" }
const y = {}
const firstValue = t =>
{ const v = Object.values(t)
if (v.length)
return v[0]
else
throw Error("empty data")
}
console.log(firstValue(x)) // "foo"
console.log(firstValue(y)) // Error: empty data
getData
Now write a generic getData. We chain our firstValue function on the end, and be careful not to add a console.log or .catch here; that is a choice for the caller to decide -
getData(url) {
return this
.sendMicroServiceRequest({ method: "GET", url })
.then(response => {
if (response.data)
return response.data
else
return Promise.reject(response)
})
.then(firstValue)
}
Now we write getCompanies, getUsers, etc -
getCompanies() {
return getData("api/authenticated/function/getcompanies")
}
getUsers() {
return getData("api/authenticated/function/getusers")
}
//...
async and await
Maybe you could spruce up getData with async and await -
async getData(url) {
const response =
await this.sendMicroServiceRequest({ method: "GET", url })
return response.data
? firstValue(response.data)
: Promise.reject(response)
}
power of generics demonstrated
We might even suggest that these get* functions are no longer needed -
async getAll() {
return {
companies:
await getData("api/authenticated/function/getcompanies"),
users:
await getData("api/authenticated/function/getusers"),
devices:
await getData("api/authenticated/function/getdevices"),
// ...
}
}
Above we used three await getData(...) requests which happen in serial order. Perhaps you want all of these requests to run in parallel. Below we will show how to do that -
async getAll() {
const requests = [
getData("api/authenticated/function/getcompanies"),
getData("api/authenticated/function/getusers"),
getData("api/authenticated/function/getdevices")
]
const [companies, users, devices] = Promise.all(requests)
return { companies, users, devices }
}
error handling
Finally, error handling is reserved for the caller and should not be attempted within our generic functions -
this.getAll()
.then(data => this.render(data)) // some Vue template
.catch(console.error)
I use the following method in odder to retrieve data by passing pageIndex (1) and pageSize (500) for each HTTP call.
this.demoService.geList(1, 500).subscribe(data => {
this.data = data.items;
});
The response has a property called isMore and I want to modify my method in odder to continue HTTP calls if isMore is true. I also need to merge the returned values and finally return the accumulated values.
For example, assuming that there are 5000 records and until 10th HTTP call, the service returns true for isMore value. After 10th HTTP call, it returns false and then this method sets this.data value with the merged 5000 records. For this problem, should I use mergeMap or expand or another RxJs operator? What is the proper way to solve this problem?
Update: I use the following approach, but it does not merge the returned values and not increase the pageIndex. For this reason it does not work (I tried to make some changes, but could not make it work).
let pageIndex = 0;
this.demoService.geList(pageIndex+1, 500).pipe(
expand((data) => {
if(data.isComplete) {
return of(EMPTY);
} else {
return this.demoService.geList(pageIndex+1, 500);
}
})
).subscribe((data) => {
//your logic here
});
Update II:
of({
isMore : true,
pageIndex: 0,
items: []
}).pipe(
expand(data => demoService.geList(data.pageIndex+1, 100)
.pipe(
map(newData => ({...newData, pageIndex: data.pageIndex+1}))
)),
// takeWhile(data => data.isMore), //when using this, it does not work if the total record is less than 100
takeWhile(data => (data.isMore || data.pageIndex === 1)), // when using this, it causing +1 extra HTTP call unnecessarily
map(data => data.items),
reduce((acc, items) => ([...acc, ...items]))
)
.subscribe(data => {
this.data = data;
});
Update III:
Finally I made it work by modifying Elisseo's approach as shown below. Howeveri **I need to make it void and set this.data parameter in this getData() method. How can I do this?
getData(pageIndex, pageSize) {
return this.demoService.geList(pageIndex, pageSize).pipe(
switchMap((data: any) => {
if (data.isMore) {
return this.getData(pageIndex+1, pageSize).pipe(
map((res: any) => ({ items: [...data.items, ...res.items] }))
);
}
return of(data);
})
);
}
I want to merge the following subscribe part to this approach but I cannot due to some errors e.g. "Property 'pipe' does not exist on type 'void'."
.subscribe((res: any) => {
this.data = res;
});
getData(pageIndex, pageSize) {
return this.demoService.getList(pageIndex, pageSize).pipe(
switchMap((data: any) => {
if (!data.isCompleted) {
return this.getData(pageIndex+1, pageSize).pipe(
map((res: any) => ({ data: [...data.data, ...res.data] }))
);
}
return of(data);
})
);
}
stackblitz
NOTE: I updated pasing as argument pageIndex+1 as #mbojko suggest -before I wrote pageIndex++
UPDATE 2
Using expand operator we need take account that we need feed the "recursive function" with an object with pageIndex -it's necesarry in our call- for this, when we make this.demoService.getList(data.pageIndex+1,10) we need "transform the result" adding a new property "pageIndex". for this we use "map"
getData() {
//see that initial we create "on fly" an object with properties: pageIndex,data and isCompleted
return of({
pageIndex:1,
data:[],
isCompleted:false
}).pipe(
expand((data: any) => {
return this.demoService.getList(data.pageIndex,10).pipe(
//here we use map to create "on fly" and object
map((x:any)=>({
pageIndex:data.pageIndex+1, //<--pageIndex the pageIndex +1
data:[...data.data,...x.data], //<--we concatenate the data using spread operator
isCompleted:x.isCompleted})) //<--isCompleted the value
)
}),
takeWhile((data: any) => !data.isCompleted,true), //<--a take while
//IMPORTANT, use "true" to take account the last call also
map(res=>res.data) //finally is we only want the "data"
//we use map to return only this property
)
}
Well we can do a function like this:
getData() {
of({pageIndex:1,data:[],isCompleted:false}).pipe(
expand((data: any) => {
return this.demoService.getList(data.pageIndex,10).pipe(
tap(x=>{console.log(x)}),
map((x:any)=>({
pageIndex:data.pageIndex+1,
data:[...data.data,...x.data],
isComplete:x.isComplete}))
)
}),
takeWhile((data: any) => !data.isComplete,true), //<--don't forget the ",true"
).subscribe(res=>{
this.data=res.data
})
}
See that in this case we don't return else simple subscribe to the function and equal a variable this.data to res.data -it's the reason we don't need the last map
Update 3 by Mrk Sef
Finally, if you don't want your stream to emit intermittent values and you just want the final concatenated data, you can remove the data concatenation from expand, and use reduce afterward instead.
getData() {
of({
pageIndex: 1,
data: [],
isCompleted: false
})
.pipe(
expand((prevResponse: any) => this.demoService.getList(prevResponse.pageIndex, 10).pipe(
map((nextResponse: any) => ({
...nextResponse,
pageIndex: prevResponse.pageIndex + 1
}))
)
),
takeWhile((response: any) => !response.isCompleted, true),
// Keep concatenting each new array (data.items) until the stream
// completes, then emit them all at once
reduce((acc: any, data: any) => {
return [...acc, ...data.data];
}, [])
)
.subscribe(items => {
this.data=items;
});
}
It doesn't matter if you're total record change as long as api response give you the isMore flag.
I'm skipping the part how to implement reducer action event i'm assuming you've already done that part. So i will just try to explain with pseudo codes.
You have a table or something like that with pagination data. on intial state you can just create an loadModule effect or using this fn:
getPaginationDataWithPageIndex(pageIndex = 1){
this.store.dispatch(new GetPaginationData({ pageIndex: pageIndex, dataSize: 500}));
}
in your GetPaginationData effect
... map(action => {
return apicall.pipe(map((response)=> {
if(response.isMore){
return new updateState({data:response.data, isMore: responseisMore})
} else {
return new updateState({isMore: response.isMore}),
}
}})
`
all you have to left is subscribing store in your .ts if isMore is false you will not display the next page button. and on your nextButton or prevButton's click method you should have to just dispatch the action with pageIndex
I do not think recursion is the correct approach here:
interval(0).pipe(
map(count => this.demoService.getList(count + 1, 500)),
takeWhile(reponse => response.isMore, true),
reduce((acc, curr) => //reduce any way you like),
).subscribe();
This should make calls to your endpoint until the endpoint returns isMore === false. The beautiful thing about interval is that we get the count variable for free.
But if you are set on using recrsion, here is the rxjs-way to do that using the expand-operator (see the docs). I find it slightly less readable, as it requires an if-else-construct which increases code complexity. Also the outer 'counter' variable just isn't optimal.
let index = 1;
this.demoService.geList(index, 500).pipe(
expand(response => response.isMore ? this.demoService.geList(++index, 500) : empty()),
reduce((acc, curr) => //reduce here)
).subscribe();
I fetched my response by hitting an API and pushed each element of the response(which is an array of objects) onto an array in the state i.e mydata
state={
mydata:[]
}
componentDidMount() {
const headers = {
"Content-Type": "application/json",
};
Axios.get("/getmeetings", {
headers
})
.then((res) => {
if (res.status === 200) {
console.log(res);
res.data.forEach((e, i) => {
this.state.mydata.push(e)
})
} else {
alert(res);
}
})
.catch((error) => console.log(error));
}
Now the problem is when I try to access the length of my state variable it gives it as 0
I tried printing the array and I can see that there are values available.
I also checked if my state variable was getting correctly populated using the chrome react extension.
Now another weird thing is when I hardcoded another object into the mydata variable
state= {
mydate = [{name:"temp"},{age:"1234"}]
}
and then again pushed my responses onto that array and later when I checked its length, it gives me 2. When I tried to print that array it shows those 2 objects plus my other pushed objects.
This is a sample object I'm trying to push
{
"agenda": "sadsadasd",
"project": "ECAM",
"meeting_title": "asdasd",
"meeting_id": "1588072890",
"attendees": [
"a##$$",
"a#a"
],
"date": "1588072890",
"host": "a#a"
}
Consider avoiding direct push since it will mutate the state. Use setState function instead.
this.setState(({ myData }) => ({ myData: [...myData, res.data] }));
I am trying to check in a form is the user exists in a MySQL Database with angular.
I am creating a localstorage item inside a .subscribe so if the user exists it creates a localstorage like this:
localStorage.setItem("exist","true");
but if it doesn't exist makes this:
localStorage.setItem("exist","false");
After the function I check if it is true or false, but it is null, because .subscribe makes it after all the functions. So if I check a 2nd time it makes the localstorage item correctly.
this.userService.getAutor(id)
.subscribe(
(data) => { // Successins
localStorage.setItem("exist","true");
},
(error) => { // error
localStorage.setItem("exist","false");
}
);
}
This is where I am calling the function
this.usuarioExiste(this.dniAutor);
if(localStorage.getItem('existe') == 'false'){
var expReg = /^(\d{8})([A-Z])$/;
if(expReg.test(this.dniAutor)){
this.userService.crearAutor(JSONform)
.subscribe(
(data) => { // Success
console.log("¡Bien HECHO!");
}
);
alert("Autor añadido correctamente");
}
else{
alert("Dni mal introducido")
}
After the function I check if it is true or false, but it is null
Wrong way
this.userService.getAutor(id)
.subscribe(
(data) => { // Successins
localStorage.setItem('existe',"true"); // <-- called after functionCheckIfTrueOrFalse, NO DATA in storage yet
},
(error) => { // error
localStorage.setItem('existe',"false"); // <-- called after functionCheckIfTrueOrFalse, NO DATA in storage yet
}
);
}
functionCheckIfTrueOrFalse() // <- is called before any localStorage.setItem()
Correct way
this.userService.getAutor(id)
.subscribe(
(data) => { // Successins
localStorage.setItem('existe',"true");
functionCheckIfTrueOrFalse() // <- localStorage HAS the data
},
(error) => { // error
localStorage.setItem('existe',"false");
functionCheckIfTrueOrFalse() // <- localStorage HAS the data
}
);
}
Knowledge
If you want to spend time and understand how it works under the hood, you can check this article.
In nutshell JavaScript handles async treads differently:
You get null because you are reading data from the sync Queue before you get data from the Async tread.
As Kurt mentioned, my answer needs an explanation.
So I'll do my best below:
You have to check first if the data is available when you are in the subscribe method due to the asynchronous nature of the call. And only then you access the local storage as you intend.
Note: You can also use the filter operator like so filter(data => data) to skip the if condition but only if you're not interested to access the local storage if the data doesn't exist.
this.userService.getAutor(id).subscribe(data => {
if (data) {
localStorage.setItem('existe', 'true');
} else {
localStorage.setItem('existe', 'false');
}
});
It finally worked!
I used the suggestion of 0leg and it worked!
this.userService.getAutor(id).subscribe(data => {
if (data) {
localStorage.setItem('existe', 'true');
} else {
localStorage.setItem('existe', 'false');
}
});
Many thanks to all!
Have a nice day!