Is there a way to invoke the value event handler only on value change in Firebase? If I just add the value event handler without any special code, it will be called once on reading the initial value and then on any value change. I want to avoid invoking the event handler on reading the initial value.
One of the use cases where I need this functionality is that I am working on an app where user can add tasks for some other users. The other users can either accept or reject a task. There is a server which is monitoring these tasks on Firebase and it sends a push notification to the creator of the task whenever a user accepts or rejects a task i.e. whenever there is a change in the status of task.
I have tried two solutions.
Solution 1:
Have a map and add an entry to the map when the value event handler is called if it is not already present and if the the entry is present, then do the work required on value change. This solution works but I need this in several different cases and I have to create this map in all such cases and I didn't find it to be a convenient solution.
Solution 2:
Add the value event handler like this
ref.on('value', (snapshot) => {
console.log('Initial call ', this.initialCall);
if(!this.initialCall) {
// Do work
} else {
this.initialCall = false;
}
}, {initialCall: true}) // {initialCall: true} is the context which is provided as this in the event handler
Here my thinking was that I can check if the event handler is called for the initial value by checking if this.initialCall is set to true. If it is, then set the this.initialCall to false and then in the subsequent invocations, this.initialCall should be false and I can do the work required for a value change. Using this solution what I observed was that the this.initialCall was undefined the first time the handler is called and then it was set to true and then it was true for all value change event handler invocations for all the tasks, not just the one for which it was set to true.
With regards to solution 2, I am not a JS expert and it is possible that due to some gap in my JS knowledge, I am doing something wrong due to which it doesn't work as I expected.
Please let me know if you have an idea/solution which can be used to call the value event handler only on value change.
A value event in Firebase is invoked immediately with the current value and then whenever the value changes. If you only care abut when the value changes, you can simply ignore the initial event:
var isInitialValue = true;
ref.on('value', function(snapshot) {
if (isInitialValue) {
isInitialValue = false;
}
else {
// TODO: handle subsequent changes
}
});
Related
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.
For one of my models, I have a simple ondelete event handler:
function validateStateDeletion(record){
if (record.Name===STATE_SUBMITTED || record.Name===STATE_CLOSED){
throw 'Cannot delete internal states '+STATE_SUBMITTED+' and '+STATE_CLOSED;
}
This does indeed work and prevents records meeting the condition from being deleted. I see the error is propagated back to the client (it is displayed in the dev console as an exception). However, capturing the exception to display something to the user, using window.onerror as part of the app initialization script, does not seem to have any effect (This may not be the correct Window object as window.onerror is undefined in the dev console, it may be some sandbox iframe where client side scripts are executed) .
window.onerror=function(message, url, line, column, error){
window.toastr.error("Error:" +(message||error));
return false;
};
Question: Any insight on global exception handling in AppMaker, or an alternative way to display server side validation errors?
>> global exception handling in AppMaker
afaik there is no such mechanism right now
>> or an alternative way to display server side validation errors?
Here we have at least 3 cases
1 Calling the server-side function
google.script.run
.withSuccessHandler(function(result) {
// TODO
})
.withFailureHandler(function(e) {
// TODO
})
.MyServerSideFunction();
2 Triggering any data-related action(createItem, saveChanges, deleteItem, load, reload... etc)
widget.datasource.createItem({
success: function (somethingThatDependsOnActionType) {
// TODO
},
failure: function (e) {
// TODO
}
});
3 Making a change to an item for a datasource in auto save mode
app.datasources.Employees.item.Name = 'Bob';
Afaik there is no good way to handle error in this case. Hope it will be fixed soon. For the time being as workaround you can switch datasource to manual save mode and pass success+failure handler to the saveChanges callback
store.select() emits previous store state.
Is it possible to subscribe to changes from "this point forward" without getting the previous store value?
If you are not interested in the first emitted value, you should be able to use the skip operator:
store.select(...).skip(1)...
skip operators need piping now, you can use skip like this:
store.pipe(select(...), skip(1));
In terms of the 'hacky' part, it is a standard practice in ngrx to set an initial state with properties set to null. and that value gets emitted initially. so the first value you get will be null in these cases.
Alternatively you could also consider skipwhile(https://www.learnrxjs.io/learn-rxjs/operators/filtering/skipwhile) and use it like this:
store.pipe(select(...), skipWhile(val => val === undefined));
where undefined is the initial value of the property you are interested in. Rather than setting the initial value of the property to undefined, you could use null as the initial value as well, and change the above skipwhile() accordingly.
Just sharing my thoughts (and solution) after reading #Niz's answer.
This is a perfect, practical example of how to utilize the difference between null and undefined. When you initialize your state with null, you're basically saying:
I don't care about differentiating the nullable future state from the
initial one. I don't care if the user is null because he has signed
out or because he just didn't sign in
However, in some cases this could be insufficient. Think about a case when you need an asynchronous call (implemented in effects) in order to know if you have an active user session. Based on the selection result, you should determine whether to show a login modal or redirect to a content page. With initial user state set to null, you'd pop up that modal and then immediately hide it when that asynchronous call returns a session value.
With initial state set to undefined you can make that differentiation, saying:
Initially, I know nothing about my state, then it's undefined. When I know it should be empty, then I'll set it to null.
Therefor, as a practical solution, I set everything on the app's initialState to undefined. In the example above, I need to know if the login modal should be displayed after the asynchronous call resolves. skipWhile(val => val === undefined) will do the job for sure, but repeating it over and over again feels a little tedious. Plus, it's not really descriptive to our use case. I created a rxjs-custom-operators.ts with a shortened implementation:
import { Observable } from "rxjs";
import { skipWhile } from "rxjs/operators";
export const skipInitial = () => {
return <T>(source: Observable <T>): Observable<T> => {
return source.pipe(skipWhile(value => value === undefined));
};
};
Usage:
navigateOnLoad(): void {
this.store.pipe(select(selectAuthUser), skipInitial()).subscribe((authUser: CognitoUser) => {
// Navigate to login if !authUser, else navigate to content...
});
}
I'm using Firebase in a website (and it's awesome).
Via several .on('child_added') and .on('value') callbacks I'm populating a local store that is bound to my UI.
When a user signs out, I want to clean their data out of my local store. What is the recommended way to react to a user signing out with Firebase? Ideally I would like to pass a callback to .unauth() to do a cleanup. But there's no such callback.
My current solution is a bit messy...
I listen with onAuth().
When onAuth triggered with a non-null value I set a variable isLoggedIn = true
When onAuth is triggered with null and isLoggedId === true then I perform a cleanup.
I don't want to do a clean up every time onAuth is called with null because it does this on page load.
If the user triggers the logout, that means your code is calling unauth(). That would also be the moment to clean up:
ref.unauth();
cleanupData();
But if the user gets signed out for another reason (i.e. their session expiring), then cleaning up in the null part of onAuth() makes sense. If you're worried about the initial null, you could wrap this in a check:
var previousAuthData = null;
ref.onAuth(function(authData) {
if (authData) {
...
}
else if (previousAuthData != null) {
cleanupData();
}
previousAuthData = authData;
}
How can I detect when a subscription is cancelled?
There are many ways it can be cancelled and I would like to analyze cancellation/subscription behavior.
Set a handle to your collection subscription, e.g.
var subHandle = Meteor.subscribe('subscription-name')
Subscription handles have a method called ready() that is a reactive data source. If the subscription has been cancelled, it will return false. Because it is a reactive data source, you could place an if statement inside a reactive computation to detect when the the subscription is cancelled.
Example:
Deps.autorun(function() {
if (subHandle && (! subHandle.ready())) {
// subscription has been cancelled
}
});
However, make sure you run this function after subHandle has been defined. This computation will not register with a reactive dependency until it calls subHandle.ready().