RTK Query - Delete cached data upon cacheEntryAdded - redux

Currently we have an api endpoint that requests a single 'Group' via ID.
We have a WebSocket subscription set up, and in the onCacheEntryAdded definition, we handle cases where that Group is updated, or deleted.
When we receive an update message from the websocket, we trigger the following;
updateCachedData((draft) => {
draft = response;
}
Which updates the entry, as expected.
However, what is the approach we should use if we want to remove the entry entirely? Upon 'delete' messages from the websocket, I would assume I could simply set draft as undefined, but that doesn't seem to be the case.

updateCachedData((draft) => {
draft = response;
}
actually does not update anything here in the first place.
updateQueryResults has the same rules as produce from immer or normal createSlice case reducers: you can modify the object in state (or draft in this case), but you cannot reassign the variable itself. If you want to do that, you have to
updateCachedData((draft) => {
return response;
}
instead.
In the same fashion, you can
updateCachedData((draft) => {
return null;
}
too, but that will not remove the full cache entry, it will only set the data to null. (undefined won't work!)
The cache entry will only be removed once there is no more component using it (by not using it with useQuery) - and then it will be removed automatically after 60 seconds.

Related

Prevent refetching data when resetting redux state on a reroute with next-router

I currently have this piece of code:
const handleClick = async () => {
dispatch(resetFilters());
if (router.pathname !== '/') {
await router.push('/');
}
};
Where resetFilters() is a function to reset all the state in a slice.
My problem is that wherever I place this function (before or after the reroute), it will cause data to be fetched twice (since what data is fetched depends on the state).
If I place it before, I fetch data based on the reset state on the page I'm rerouting away from (which I won't use)
If I place it after, I fetch data based on the old state on the page I'm rerouting to, which then has to be fetched again with the reset state.
I saw that react-router-redux has a LOCATION_CHANGE action which seems to solve my problem.
Is there an equivalent version for next-router?
I.e. I need something which allows me to update redux state and redirect with next-router in an atomic step.

Is it possible to destroy firestore listeners soon if client is not connected? [duplicate]

Is there any way to pause firestore listener without removing it?
I have multiple firebase listeners, some are dependent on other, that changes or start other listeners on data change. Lets say my first listener starts a second listener its onSnapshot. First listener started on useEffect. For certain condition I may not want to change the second listener, so I need to discard data change update from first listener.
If condition met (button click), I discard data changes on first listener for a few moments. Currently I'm doing this using a boolean with useRef. My react app is working fine, with dependant listeners like this. I could remove the listener but I do not want to remove and recreate the listener.
I was wondering if there is a pausing mechanism or method available for any listener. I think it will save a tiny read cost if there was such a method because I'm not using that data sent onSnapshot.
Code example:
useEffect(() => {
let firstListener, secondListener;
//console.log("useEffect...");
function ListenerFunc(p) {
secondListener = await firestore
.collection("test")
.doc(p)
.onSnapshot((doc) => {
//console.log("Current data: ", doc.data());
//Need to discard unwanted change here.
//Changing it on button click for a 2 seconds then it changes back to : pauser.current = false.
if (pauser.current) {
console.log("paused for a moment.");
//pauser.current = false;
return;
}
else {
//update.
}
})
}
firstListener = firestore
.collection("test")
.doc("tab")
.onSnapshot((doc) => {
//console.log("Current data: ", doc.data());
var p = doc.data().p; //get variable p
ListenerFunc(p);
});
// cleanup.
}
Unfortunately this is not possible. If you need to stop listening for changes, even temporarily, you have to detach your listener and attach a new one when you want to start listening again, there is no pause mechanism for listeners.
You could open a Feature Request in Google's Issue Tracker if you'd like so that the product team can consider this, but given that this has already been proposed in this GitHub Feature Request for the IOS SDK and it was rejected I don't see this changing anytime soon.

Meteor with Angular2 , Fetching all entries from a collection in single shot

I have successfully integeraed meteor with angular2 but while fetching the data from collection facing difficulties in getting at one shot, here is the steps:
Collection Name : OrderDetails
No Of records : 1000
Server:
Created publication file to subcribe the collection:
Meteor.publish('orderFilter', function() {
return OrderLineDetails.find({});
});
Client:
this.dateSubscription =
MeteorObservable.subscribe('orderFilter').subscribe(()=> {
let lines = OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:
{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).fetch();
});
In this lines attribute fetches all the collection entries, but fails to subscribe for the changes
When I try with below one,
OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).zone().subscribe(results => {
// code to loop the results
});
In this am able to subscribe for the collection changes, but the results are looped for 1000 times , as 1000 entries in the colleciton.
Is there any way to get the whole collection entries in one single shot and mean time to subscribe the changes in the collection ?.
Yes, there are a couple of ways you can do it, mostly depending on how you want to handle the data.
If having everything at once is important, then use a Method such as:
MeteorObservable.call('getAllElements', (err, result) => {
// result.length === all elements
})
While on server side doing
Meteor.methods({
getAllElements:function(){return myCollection.find().fetch()}
})
Now, if you want to listen to changes, ofcourse you'll have to do a subscription, and if you want to lower the amount of subscriptions, use rxjs' debounceTime() function, such as (from your code):
this.theData.debounceTime(400).subscribe(value => ...., err =>)
This will wait a certain amount of time before subscribing to that collection.
Now, based on your intent: listening to changes and getting everything at once, you can combine both approaches, not the most efficient but can be effective.
As #Rager explained, observables are close to streams, so when you populate data on miniMongo (front end collection you use when you find() data and is populated when you subscribe to publications) it will start incrementing until the collection is in sync.
Since miniMongo is populated when you subscribe to a publication, and not when you query a cursor, you could either:
Try the debouceTime() approach
Use a Meteor.Method after subscribing to the publication, then sync both results, keeping the first response from the method as your starting point, and then using data from Collection.find().subscribe(collectionArray => ..., err=>) to do whatterver you want to do when changes apply (not that recommended, unless you have a specific use case for this)
Also, .zone() function is specific to force re-render on Angular's event cycle. I'd recomend not use it if you're processing the collections' data instead of rendering it on a ngFor* loop. And if you're using an ngFor* loop, use the async pipe instead ngFor="let entry of Collection | async"
I don't think that's possible. When you subscribe to an Observable it handles values as a "stream", not necessarily a loop. I have seen some makeshift helper methods that handle the data synchronously, though the time it takes to subscribe is not decreased. Check out this article for an under the hood look... A simple Observable implementation
However, you can set it up to only loop once.
The way that I've been setting up that scenario, the collection only gets looped through one time (in the constructor when the app starts) and detects changes in the collection. In your case it would look like:
values: YourModel[] = []; //this is an array of models to store the data
theData: Observable<YourModel[]>;
errors: string[];
subFinished: boolean = false;
constructor(){
this.theData = OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).zone();
MeteorObservable.subscribe('orderFilter').subscribe();
//push data onto the values array
this.theData.subscribe(
value => this.values = value,
error => this.errors.push("new error"),
() => this.subFinished = true
);
}
The "values" array is updated with whatever changes happen to the database.

How do I make an Angular2 application update instantly on all connected devices as the events are fired?

I need to build a restaurant management mobile application with Angular2 and Ionic2 and a website for the same restaurant with Angular2 that constantly make http connections to store and retrieve data from the database in order to maintain the latest data.
For example, If an order request was fired from a waiter's mobile phone, the new order is posted to the database, which I can accomplish. the chef needs to get the instant notification that a new order has been created. Also, the data needs to be reflected in other employees' phones and also on the website.
All I can come up with is the use of setInterval, but since I've never done anything like this before, I'm not sure if this is the correct way.
component
orders: Order[];
constructor(private orderService: OrderService) {
setInterval(function() {
this.orderService
.getOrders()
.subscribe(
(orders: Order[]) => this.orders = orders,
(error: Response) => console.log(error)
)}, 3000);
}
placeanOrder(order) {
this.orderService.postOrder(order);
}
service
getOrders() Observable<any>{
this.http.get(...).map(...);
}
placeOrder(order) {
this.http.post(...).map(...);
}
When I try something like this, I get the same error logged to the console every second.
Cannot read property 'getOrders' of undefined
Why am I getting the error?
How would I convert the Observable json data retrieved from server into my interface data type, in this case, of type Order?
What is a better approach to this?
Cause wrong usage of callbacks! You have to use the arrow-syntax!
wrong: setInterval(function() {
right: setInterval(() => {
Just describe your function with correct types:
getOrders(): Observable<Order[]> {
Nothing else needed. Property-names needs to be the same (interface/json)!
It's ok to use a timer. "Better" could be a never-closed-connection to the server, so server could SEND you that data and client don't have to POLL.
https://www.websocket.org/
http://socket.io/

Meteor GroundDB granularity for offline/online syncing

Let's say that two users do changes to the same document while offline, but in different sections of the document. If user 2 goes back online after user 1, will the changes made by user 1 be lost?
In my database, each row contains a JS object, and one property of this object is an array. This array is bound to a series of check-boxes on the interface. What I would like is that if two users do changes to those check-boxes, the latest change is kept for each check-box individually, based on the time the when the change was made, not the time when the syncing occurred. Is GroundDB the appropriate tool to achieve this? Is there any mean to add an event handler in which I can add some logic that would be triggered when syncing occurs, and that would take care of the merging ?
The short answer is "yes" none of the ground db versions have conflict resolution since the logic is custom depending on the behaviour of conflict resolution eg. if you want to automate or involve the user.
The old Ground DB simply relied on Meteor's conflict resolution (latest data to the server wins) I'm guessing you can see some issues with that depending on the order of when which client comes online.
Ground db II doesn't have method resume it's more or less just a way to cache data offline. It's observing on an observable source.
I guess you could create a middleware observer for GDB II - one that checks the local data before doing the update and update the client or/and call the server to update the server data. This way you would have a way to handle conflicts.
I think to remember writing some code that supported "deletedAt"/"updatedAt" for some types of conflict handling, but again a conflict handler should be custom for the most part. (opening the door for reusable conflict handlers might be useful)
Especially knowing when data is removed can be tricky if you don't "soft" delete via something like using a "deletedAt" entity.
The "rc" branch is currently grounddb-caching-2016 version "2.0.0-rc.4",
I was thinking about something like:
(mind it's not tested, written directly in SO)
// Create the grounded collection
foo = new Ground.Collection('test');
// Make it observe a source (it's aware of createdAt/updatedAt and
// removedAt entities)
foo.observeSource(bar.find());
bar.find() returns a cursor with a function observe our middleware should do the same. Let's create a createMiddleWare helper for it:
function createMiddleWare(source, middleware) {
const cursor = (typeof (source||{}).observe === 'function') ? source : source.find();
return {
observe: function(observerHandle) {
const sourceObserverHandle = cursor.observe({
added: doc => {
middleware.added.call(observerHandle, doc);
},
updated: (doc, oldDoc) => {
middleware.updated.call(observerHandle, doc, oldDoc);
},
removed: doc => {
middleware.removed.call(observerHandle, doc);
},
});
// Return stop handle
return sourceObserverHandle;
}
};
}
Usage:
foo = new Ground.Collection('test');
foo.observeSource(createMiddleware(bar.find(), {
added: function(doc) {
// just pass it through
this.added(doc);
},
updated: function(doc, oldDoc) {
const fooDoc = foo.findOne(doc._id);
// Example of a simple conflict handler:
if (fooDoc && doc.updatedAt < fooDoc.updatedAt) {
// Seems like the foo doc is newer? lets update the server...
// (we'll just use the regular bar, since thats the meteor
// collection and foo is the grounded data
bar.update(doc._id, fooDoc);
} else {
// pass through
this.updated(doc, oldDoc);
}
},
removed: function(doc) {
// again just pass through for now
this.removed(doc);
}
}));

Resources