Observables with ngrx/store - race conditions - javascript

I have a problem that I believe is due to a combination of my initial state in ngrx-store and trying to get just the initial response from an observable. I'm using datatables, and large amounts of data for a static report - so I just want the first "real" response from:
Observable.zip(this.sites$, this.devices$, this.machines$, this.machineContent$)
In my ngrx reducer I have the initial state defined as [] for each of these. However, at times there is (what I think is a race condition) where one of those observables will be returned as [], but the Observable.zip resolves, and it incorrectly renders my table at that point.
I've tried various combinations of:
Observable.zip(this.sites$.skip(1), this.devices$.skip(1), this.machines$.skip(1), this.machineContent$.skip(1))
and
Observable.zip(this.sites$.skip(1), this.devices$.skip(1), this.machines$.skip(1), this.machineContent$.skip(1))
.take(1)
And these work about 80% of the time.
I also have an observable of 'isLoaded' for each of those observables (machinesIsLoaded, machineContentIsLoaded, etc.) which I thought about using with .takeUntil, except that I'd have to check for each of these to return true, and it feels like I must be doing something wrong.
Any ideas?

First: make sure your reducer is not ever mutating the array in state, because that will cause you problems. Make sure you are cloning the array or using an ImmutableArray.
Second: instead of filter, use skipWhile
function isEmpty(table: any[]) : boolean { return !table || !table.length; }
Observable.zip(this.sites$.skipWhile(isEmpty), this.devices$.skipWhile(isEmpty), this.machines$.skipWhile(isEmpty), this.machineContent$.skipWhile(isEmpty));
This will ignore results until the arrays get populated and then always use results even if the arrays empty again.

Related

Redux best practice to filter data

in the process of developing an application, I'm facing a question about whether I'm using Redux correctly.
I have a fav:[] in which I add product objects and render their list. However, in order for the data not to be lost, I have to copy this fav:[] to favCopy:[] and only after that execute .filter
Example code:
case "fav":
state.fav = action.payload.filter === 'all'
? state.favCopy
: state.favCopy.filter((item: any) => item[type] === action.payload.filter)
break;
I would like to understand how right I am by keeping the original array intact? Maybe there is a way not to multiply arrays and use only one state?
We would recommend not doing filtering directly in the reducer most of the time. Instead, keep the original array in state as-is, and then also store a description of how you want the filtering to be done. From there, use selector functions to derive the filtered value as needed:
https://redux.js.org/usage/deriving-data-selectors

Access the data from two observables without multiple subscriptions

My first observable emits data concerning my user (from Firebase). On this data being emitted, I'd like to trigger a second call to Firebase, retrieving various extra user information from a distinct collection.
I can manage the two operations individually just fine, but obviously the latter call should only really take place after the first call has succeeded. I imagine this is something to do with mergeMap but I can't figure out quite how to do it. Should point out to the close/downvoter that I've, despite trying, not found similar questions that are both answered and present the solution in a way I can match to exactly what I'm failing to understand.
this._auth.userState$
.mergeMap(user => this._userMetaData.getData(user.uid))
.subscribe(data => {
console.log(data);
});
This is my initial effort. I was hoping that the console.log would return (in some fashion) the data from both calls. It just logs an empty array, instead.
Can anyone point me in the right direction here?
I see 2 different points worth to be highlighted.
1) Within the submit, you want to access the data returned by the 2 calls:
This can be accomplished in the following way
.mergeMap(user =>
this._userMetaData.getData(user.uid)
.map(data => ({user, data})
)
Here you basically create, via the last map, a return which is an object referencing both the user returned by the first call and the data returned by the second call
2) In the log you see an empty Array:
Here you need to look at your logic since, according to your current code, in the log you should see the result of the second call

Vue 2 mutating a data property inside a v-for causes infinite loops / problems

Although I could not find in the documentation, it appears that mutating a data array property isn't a good idea, even when it's not being rendered out to the view.
See this fiddle: https://jsfiddle.net/078v5142/3/
I need to decrement conditionalSet. I'm using pop() on every v-for loop to check the condition of an index. I can't use a computed property because i need to pass the index.
I can't just copy the conditionalSet array either because it needs to be tracked when a condition is set and popped.
This is a greatly simplified problem that I'm facing.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js! I do not like loops, all that much.',
imageSet: [
'a','b','c','d','e','f','g', 'h', 'i', 'j'
],
conditionalSet: [1,2]
},
methods: {
doShow(index){
if(this.someInnerConditionThatsNotRenderedOut()){
if( index === 1 || index === 4 || index === 5 || index === 6){
this.conditionalSet.pop(); // <-- this is the problem, but how to I track?
return true;
}
}
console.log('I should no show index 5 and index 6')
},
someInnerConditionThatsNotRenderedOut(){
return true //comment this out. No error.
return this.conditionalSet.length > 0
}
}
})
The short answer
To cut a long story short, I think you can (and should) use a computed property for displaying the filtered list. I'm not 100% clear what you were trying to achieve in your example, but hopefully this fiddle gives you a pointer: https://jsfiddle.net/dtchqpjd/ If you look at the console when it runs, you'll be able to see how the logic pans out.
The longer answer
The problem is really caused by a combination of three things:
Your doShow function has a side-effect.
That side-effect is related to a data property, which is 'watched' by VueJS.
That data property is an array, which you need to 'get' in order to modify/pop.
VueJS has a reactivity system which watches data and, when it changes, determines the effect this will have on the DOM. This allows it to efficiently update the DOM in response to data changes, without needing to completely re-create the DOM every time something changes.
In this case, VueJS knows the following:
You get the conditionalSet property. You have to do this to call the pop method. But Vue doesn't know what you did with the value returned.
You change the conditionalSet property by calling the pop method.
Because you get and set the property, VueJS assumes that the result returned by the doShow function is stale, and hence re-evaluates it, and then you get an infinite loop. It can't see that you only 'got' it because you wanted to 'pop' it!
When you perform the filtering and modification all within a computed property, you avoid confusing VueJS (and the infinite loop) as it can see that the result is stable and doesn't need to be re-computed. It also happens to be much clearer code since the logic is all in one place. However, your computed property still has a side-effect which, if maybe if I understood more about what you were trying to do, could probably be avoided too.
A small illustration
To illustrate what's happening, here's a slight modification to your fiddle, where I've taken a separate (unknown to Vue) reference to the array. When I use that reference to call 'pop', you don't get the infinite loop: Vue can still see that we've changed the array, but because we didn't 'get' it, it doesn't assume that the output of the function is now stale. Uncomment the call which just 'gets' the array, and the problem comes back: Vue thinks it needs to re-evaluate the function.
Interestingly, Vue doesn't appear to look at the order of the dependencies: it doesn't matter if you 'get' then 'set', or 'set' then 'get'. It's just the fact that you did both which causes the circular dependency - they get added as dependencies and lead to a re-evaluation on the queue.
Commenting out the return true statement avoids the infinite loop simply because it prevents the array from being continually 'popped': once the array has been popped twice, your someInnerConditionThatsNotRenderedOut function returns false, and the potential for an infinite loop is cut short.
You just thought hard almost ;)
Imagine one computed method which filters your imageSet and returns which result you want and your v-for act on it.
Your fiddle with some changes:
https://jsfiddle.net/5dbakawb/

immutablejs (react/redux) , "push" into state with immutable

In my react/redux app, I am setting states in my reducers using something like this :
state.set('search', myResults);
the state is an immutablejs Map(). The format of the object when toJS() is run on it (no longer an immutable object) is like so :
{
results: { ..all results in here }
}
So you'd have state.search.results.
and this works great, however I have a scenario where I need to "push" into the search part of this map. I was thinking merge would take care of this however it does not seem to work as I assumed it would. I have tried :
state.mergeIn('search', singleResult);
as well as
state.merge({ 'search': singleResult });
neither seem to be working. The intended result is to have that single result pushed into the state Map() (and not overriding it like with .set). So the desired result would end up looking like this :
{
results: { singleResult pushed or merged into here with other results }
}
I am unsure how to do this with immutablejs, any advice would be greatly appreciated. Thanks!
You need to use update for this. Merging does not concat arrays together in the key.
state.update('search', search => search.concat(singleResult));
Also be aware that if search contains a normal JS array - then the array itself is still an mutable data structure. Immutable.JS doesn't do deep conversion of datatypes unless you use fromJS().

large arrays in dependent observables - cascading

I am using Knockout JS, as business requirements dictate that most, if not all logic is processed in the browser due to low bandwidth users. It's working out awesome so far except for one issue.
I am using a number of multiselect dropdown lists that all contain cascading logic. I have, say 8 lists that process hierarchical data and alter selectable options in child lists.
This is all good until I get to the bottom 2 lists which could potentially contain 3000 items depending on parent list selections (especially when 'select all' is clicked).
the problem is, in IE, I'm getting long running script warning messages, which I need to get rid of. Here's some code:
viewModel.BottomLevelList= ko.dependentObservable(function () {
if (this.ParentList().length === 0) { //nothing selected
return [];
}
var result = [];
var i = self.longMasterList.length;
var currentId = 0;
while (i--) {
//psuodo code:
//this.ParentList().Contains(loop-item) then
//put in return list based on some further logic
//else continue
}
return result;
}, viewModel);
I have tried using various setTimeout techniques from SO to break the large array up and return control momentarily to the browser, but with no success. The result is never returned and / or the observable seems to detach itself leaving an empty list in the UI.
If I need to use AJAX I will, but this is a very last resort and would prefer to keep it in the client.
So my question boils down to:
How can I stop long running script warnings as a result of processing large data sets (in the context of Knockout JS dependant observables and cascading lists)
Is there some idiomatic JavaScript technique I could / should be using in this scenario
Am I not seeing the wood for the trees here?!
Thanks muchly for any help
I would first suggest that you optimize your dependentObservable.
When you read any observable, Knockout registers a dependency to it in Dependency Manager. It contains pretty simple code like this:
function registerDependency(observable) {
if (ko.utils.arrayIndexOf(dependencies, observable)) {
dependencies.push(observable);
}
}
I can see in your pseudo-code that you are accessing this.ParentList() in the while loop. It means registerDependency will be called 3000 times and the dependencies array will be scanned 3000 times, which is bad for IE (since it has no built-in Array.indexOf method).
So my number one suggestion would be: Read all observables before loops.
If it doesn't help, I suggest that you proceed with setTimeout(). It is a bit tricky. Please check out this example: http://jsfiddle.net/romanych/4KGAv/3/
I have defined asyncObservable. You should pass an array with all dependencies of your dependentObservable. When ko.toJS is called, all observables are unwrapped. Then we call the passed callback function with arguments enumerated in the dependencies array. This function will be evaluated async.
I have wrapped this code into ko.dependentObservable to re-evaluate loader callback on any change of passed elements passed in dependencies
UPDATE:
My code was overcomplicated for this issue. throttle extender will do the trick. Please checkout this sample: http://jsfiddle.net/romanych/JNwhb/1/

Categories

Resources