I have a search bar, and once the user enters any word or words (eg: "green plant") I split those words with space and array would look like words = ["green","plant"]. Now, I need to use each word as a value to my filter api which indeed returns the data that has the search word in the title or name.
The issue is that, each time it calls it overwrites the existing state so that whatever data I got for word "green" is overwritten with the next api call for "plant".
My Code for setting them,
if (response.status === 200) {
setProduct(response.data.product)
}
I also tried to use spread operators,
if (response.status === 200) {
setProduct([...product,response.data.product])
}
and also tried,
if (response.status === 200) {
setProduct((prevProducts) => [
...prevProducts.product,
response.data.product,
]);
}
Error for the above TypeError: Invalid attempt to spread non-iterable instance
response.data.product:
[
{
"_id":"61daa6401d0f202659003c12",
"name":"TIMA - Top Class Blanket",
"originalPrice":1599,
"storePrice":1500,
"discount":6.2,
"description":"Single Blanket , Size: 226*150 cm",
"category":"blankets",
"quantity":10
},
{
"_id":"61daa6401d0f2026592eef3",
"name":"Flora Bed",
"originalPrice":2599,
"storePrice":1500,
"discount":7,
"description":"Bed , Size: 226*150 cm",
"category":"bed",
"quantity":10
}
]
You're most likely encountering a closure issue, where you are looping through the two API calls, but those get initialized with the same state A.
This would mean that when you want to update the state from A->B in the first call, then B->C in the second, what actually happens is that you get A->B followed by A->C, without the changes from B.
I recommend using the Promise.all (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) to await for all results, then set your state at once. If you really want to do it sequentially, then make sure to await the first one, set your state after it, then do the same with the second one. For this latter solution (not recommended), make sure to use the callback setProduct((prev) => [...prev, newValue]).
Related
I have this reducer state chunk, that is causing a runtime error
(TypeError: (intermediate value)(intermediate value)(intermediate value) is not iterable)
since, at a certain point, the object might not be available in the state, any idea how to prevent the run time from stumbling on the part of chunk that is not available (childGroup) at the current moment? and run it only when certain criteria are met I tried adding ?? [] before the filter but typescript errored out cannot call filter on never. the issue happens when I want to delete the parent group only when there is no child group! , if there is a child group works like charm.
// REMOVE GROUP
case types.DELETE__GROUP:
return {
...state,
parentGroup: {
...state.parentGroup,
[action.payload.parentId!]: state.parentGrou[action.payload.parentId!]?.filter((el) => el.id !== action.payload.id),
},
childGroup: {
...state.childGroup,
[action.payload.parentId!]: [...state.childGroup[action.payload.parentId!]?.filter((el) => el.parentId !== action.payload.id)],
},
};
Conditional indexed access is spelled ?.[] in both TypeScript and JavaScript. So in addition to the ?.filter you need to add ?. to your [action.payload.parentId!] index, making that entire line:
[action.payload.parentId!]:
[
...state.childGroup?.[action.payload.parentId!]?.filter((el) => el.parentId !== action.payload.id)
],
But consider in investing in some utilities around this to make it clearer what the actual business logic is (for example Immer.js)
I have the following example saved in a array in redux:
example: [
{ type: "A", value: 25 },
{ type: "B", value: 23 },
{ type: "C", value: 324 },
{ type: "A", value: 25 },
]
As you can see, some of the objects may appear multiple-times, this should not be changed.
Now i try to remove an specific objects from the list without using the index. This is quite easy, because i can check if the object is the object to remove based on the comparsion below:
example[0] == example[2] // => false
example[0] === example[2] // => false
example[0] == example[0] // => true
example[0] === example[0] // => true
The problem is the aynchronous behavior of [].filter which i use to remove a entry in my reducer as you can see implemented here:
case EXAMPLE.REMOVE:
return state.filter((obj) => obj !== action.payload.objectToRemove)
Because when i remove the entries very fast, it sometimes happen than a entry may was removed during the filter but another filter-function also currently runs and then the previously removed object from the other filter-function will be back in the state.
How can i ensure that the reducer works synchronous to avoid such racing-conditions or is there even a more elegant way?
PS: I'm quite sure that the asynchronous-behavior of redux is wanted and the right-way to do it, may i need an other
Edit: I was able to solve the problem due to the fact, that as described in the comments it was not caused by an async call of the reducer but by a rerender of each component caused by the fact that i've not assigned a key and all entries which came after the entry to remove were unmounted and mounted again in the ui were rendered a component per entry of the array. Seems like i missunderstood the real causing problem at this point.
Application
A simple Search bar and a button where user enters a keyword and the response returned is from a RESTful server (HTTP GET requests)
simplesearch.ts
export class SimpleSearch {
kw: string; // keyword
resp: string; // response from Server
}
simplesearch.service.ts
Has a simple method called searchData which does a HTTP GET request with the user's keyword as a query search. (Code not included for brevity)
simplesearch.component.ts
/*All respective headers and #Component removed from brevity*/
const OUTPUT: SimpleSearch[] = []; // create an array for storing Objects
export class SimpleSearchComponent {
Output = OUTPUT; // define variable for array
constructor(private httpServ: SimpleSearchService, private temp: SimpleSearch) {}
/*Search button on HTML file does this*/
Search(inputVal: string) {
this.temp.kw = inputVal; // store the value of user's input
this.httpServ.searchData(inputVal)
.then(res => this.temp.resp = res); // store the response in temp.resp
// push the Object on the Output Array
this.Output.push({kw: this.temp.kw, resp: this.temp.resp});
}
}
Interpolation Variable
I use Output as an Interpolation Variable for my HTML template. I show the data in an unordered list
<ul>
<li *ngFor="let keyword of Output">
<span>{{keyword.kw}}</span>
</li>
</ul>
Response:
<ul>
<li *ngFor="let answer of Output">
<span>{{answer.resp}}</span> <!-- WHAT TO DO HERE for Array Index-->
</li>
</ul>
Result
I can see the keywords in a list every time a user inputs new keywords but
the responses in the wrong way
How do I pass Indexing with the Interpolation? Or am I thinking wrong?
The easy way out was to create two separate Array<String> for keywords and responses and this works great since I can use the index to delete the contents on the page too but with an Object in an Array I am confused with the key: value representation and the index of the Array (OUTPUT) itself.
The problem lies exactly where developer noticed, this.temp.resp is outside the async function. So when you are pushing items in your Output array, it's always pushing the previous search with the new keyword, therefore you are getting the behavior that the resp is always "one step behind". You can check this to understand this async behavior: How do I return the response from an Observable/http/async call in angular2?
So let's look at the code and explain what is happening. I assume you have initialized 'temp' since it isn't throwing an error on first search, where temp.resp would be undefined unless temp is initialized.
this.httpServ.searchData(inputVal)
// this takes some time to execute, so the code below this is executed before 'this.temp.resp' has received a (new) value.
.then(res => this.temp.resp = res);
// old value of 'temp.resp' will be pushed, or if it's a first search, empty value will be pushed
this.Output.push({kw: this.temp.kw, resp: this.temp.resp});
So how to solve this, would be to move the this.Output.push(... line inside the callback (then), so that the correct values will be pushed to the array.
Also I'd change your model to be an Interface instead of Class. But as to how to change the function and do the assignment inside the callback, I'd also shorten the code a bit and do:
Search(inputVal: string) {
this.httpServ.searchData(inputVal)
.then(res => {
// we are pushing values inside callback now, so we have correct values!
// and 'SimpleSearch' stands for the interface
this.Output.push(<SimpleSearch>{kw: inputVal, resp: res});
});
}
}
This should take care of it that the corresponding keyword will have the corresponding response! :)
PS. Worth noticing here, is that you'd maybe want to display the keyword while we are waiting for the response for that keyword, I ignored it here though and applied the same as you are currently using.
I am trying to make a Meteor app to let users push a value to the database. It works ok, but there a small issue. As soon a certain user has pushed his information, i don't want to let the same user create another entry. Or this must be blocked, or the value the user is pushing must be overwritten for the value he is posting the second time. Now I get multiple entry's of the same user.
Here is my code. Hope you can help me here. Thanks in advance.
Estimations.update(userstory._id, {
$addToSet: {
estimations: [
{name: Meteor.user().username, estimation: this.value}
]
}
});
From the mongo docs
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
Since your array elements are objects the value is the entire object, not just the username key. This means a single user can create multiple name, estimation pairs as long as the estimation value is different.
What you can do is remove any value for the user first, then reinsert:
var username = Meteor.user().username;
Estimations.update({ userstory._id },
{ $pull: { estimations: { name: username }}}); // if it doesn't exist this will no-op
Estimations.update({userstory._id },
{ $push: { estimations: { name: username, estimation: this.value }}});
By way of commentary, you've got a collection called Estimations that contains an array called estimations that contains objects with keys estimation. This might confuse future developers on the project ;) Also if your Estimations collection is 1:1 with UserStorys then perhaps the array could just be a key inside the UserStory document?
I've found all kinds of useful posts/info about $q, $q.all, and chaining in general, but I haven't found any examples of my exact issue, so I'm wondering whether what I want to do is even possible. I've got a series of calls I need to make, and each is dependent on the previous BUT there's a step in the middle where the series splits into several parallels. The gist of it goes something like this:
call #1: get keyword (based on inputs)
call #2: with keyword, get list of items
call #3: with item ID#s, get item profiles
call #4: with data in profiles, get item images
(I know, I know, four calls for just one is ridiculous, let alone sixteen calls total, but this is for a prototype. I don't have to be ultra-speedy. I just need to prove I can get to that final step with the existing data/calls.)
Anyway, this means that #1 is one call for all... and then as of #2, it splits up and I have to do #2, #3, and #4 for each different ID# I got from #1's return. I've mocked up a plunk using jsonplaceholder. It should retrieve the first set, then do the next two calls with the retrieved IDs, one loop per ID.
I've tried a simple for loop (which Plunker tells me I can't do w/a function in it). I've tried an angular.forEach (inside a $scope.watch and outside it), but that doesn't seem to get me anything at all:
angular.forEach($scope.resultsb, function(value, key) {
console.log(key + ': ' + value);
$http.get('http://jsonplaceholder.typicode.com/users?id='+ value)
.then(function(res2){
$scope.data2 = res2.data;
var obj = {
title: $scope.results1[i].title,
id: $scope.data2[i].id,
username: $scope.data2.username
};
$scope.results2.push(obj);
I think the forEach just isn't kicking in properly b/c it can't really run until after I get $scope.results1 (the first step's results), but can I chain an $http call to a loop?
You can see the full thing (in its current version, which may change since I continue to beat at it) at http://plnkr.co/edit/CjXt7E?p=preview. Is what I'm trying even possible, or do I have to do this long-hand?
Your for each will run even without response as it is Asynchronous, instead make a function and call it when you get the callback in .then()
Edited your plunker: http://plnkr.co/edit/mxvvCB?p=preview
like this:
$scope.Call = function(value) {
if(typeof value[$scope.counter] !== 'undefined'){
value = value[$scope.counter].id;
var i =$scope.counter;
console.log(value+' ' +i);
$http.get('http://jsonplaceholder.typicode.com/users?id='+ value)
.then(function(res2){console.log(res2);
$scope.data2 = res2.data;
var obj = {
title: $scope.results1[i].title,
id: $scope.data2[0].id,
username: $scope.data2.username
};
$scope.results2.push(obj);
$scope.counter++;
$scope.Call($scope.resultsb);
});
}
};
I hope it is what you are trying to achieve.
I GOT IT TO WORK.
This might be the worst case of function-use ever, but it works, and I get results. Not entirely -- I think some of the calls time out, so I figured out I need to ask for, like, 6, and then I get at least the 4 results I want. Which is probably indicative of the fact that this desperately calls for a single endpoint instead of doing the logic on the frontend, but it works enough for a prototype, so I'm happy (for now).
In my controller, I get the first set of IDs, and using a simple for-loop, I assign a loop-id to each. Then I call getUser(), with num (to connect the info to the first set of results), and id (to connect to the actual user I'm tracking). Here's the function w/ its internal chains:
function getUser(num, id) {
$http.get('http://jsonplaceholder.typicode.com/users?id='+ id)
.then(function(res2){
$scope.data2 = res2.data;
if ($scope.data2[0].username !== 'undefined') {
var obj = {
num: $scope.results1[num].num,
id: $scope.results1[num].id,
title: $scope.results1[num].title,
username: $scope.data2[0].username
};
$scope.results2.push(obj);
}
return $http.get('http://jsonplaceholder.typicode.com/comments?id=' + id);
}).then(function(res3){
if ($scope.results2[num].username !== 'undefined') {
$scope.data3 = res3.data;
var obj = {
num: num,
id: $scope.results2[num].id,
title: $scope.results2[num].title,
username: $scope.results2[num].username,
email: $scope.data3[0].email
};
$scope.results3.push(obj);
}
});
}
You can see the entire thing at http://plnkr.co/edit/CjXt7E?p=preview -- it shows the output of each step (results1, results2, results3). Hopefully I won't have to do something this crazy again, but just in case, maybe this will help someone. Or maybe it'll cause someone to leap up and tell me everything I did wrong, but that'd be fine, too. Gotta learn somehow. :)