Register Firebase Listener Without Calling It? - javascript

Is it possible to register a Firebase listener function without calling it when you register it?
For example:
this.gamestateURL.on('value', function(snapshot){
self.GameStateChangeEvent(snapshot);
});
GameStateChangeEvent function fires immediately upon setting up the listener.
Thank you.

Unfortunately, no. The docs specifically state:
This event will trigger once with the initial data stored at this location, and then trigger again each time the data changes. The DataSnapshot passed to the callback will be for the location at which on() was called. It won't trigger until the entire contents has been synchronized. If the location has no data, it will be triggered with an empty DataSnapshot (val() will return null).
You could, however do something like this:
var ref = this.gamestateURL // or however you create a ref
function doSomethingWithAddedOrChangedSnapshot(snapshot) {
// this function is called whenever a child is added
// or changed
}
// assuming you have "timestamp" property on these objects
// it will not be called back with any snapshots on initialization
// because the timestamp of existing snapshots will not be greater
// than the current time
ref.orderByChild('timestamp')
.startAt(new Date().getTime())
.on('child_added', doSomethingWithAddedOrChangedSnapshot);
ref.on('child_changed', doSomethingWithAddedOrChangedSnapshot);
ref.once('value', function(snapshot){
// get the initial state once
// this snapshot represents all the items on this ref
// this will only fire once on initialization
initializeGameData(snapshot.val());
});
In English:
Create one function that handles the updated/added child
start listening to the child_added event for all children added after the current timestamp (unix time since epoch). This also assumes you're storing the timestamp on these children.
start listening to the child_changed event for any child that is changed.
grab all the values of the ref once to initialize your data.
not sure if your use case needs to handle 'child_removed' or 'child_moved'

Related

Firebase onSnapshot state management issue

I recently ran into an issue with firebase (onSnapshot) realtime updates. The problem is that onSnapshot updates the state whenever a document is (created, deleted, updated) which overrides the state.
In other words, let's say I have a variable called state.
let state = null;
// And when I visit (**/homepage**) ,,,, onSnapshot runs.
firebase.collection(someCollection).onSnapshot((snapshot) => {
state = (we save documents we got from collection in state variable);
})
// I display these documents on the /homepage.
// Now, I click and call a function (orderByTitle)
// this function gets docs from firebase ... ordered by title.
async function orderByTitle(){
let docs = await firebase.collection(someCollection).orderBy("title").get();
state = docs; // (it overrides the "state" with docs oredered by title & display on page.
}
// Now, I delete one of the docs.
// The problem starts here as it triggers (onSnapshot) again
// and my ("state" variable) gets override with "unordered docs" again.
So, my question is how you prevent (onSnapshot) from overriding your current state or do you manage two different states? And if you manage two different states then how you remove the current elements from the DOM which are using old state and you force them to use other state.
The .onSnapshot function will update the data each time there's a change in the database which is why state keeps changing.
It appears that you want to read the data once. From the docs
You can listen to a document with the onSnapshot() method. An initial
call using the callback you provide creates a document snapshot
immediately with the current contents of the single document. Then,
each time the contents change, another call updates the document
snapshot
on the other hand, if you want to read data once and not have it continually update use the .get function. Here's an example of getting all documents from a collection once, with no further notifications.
db.collection(someCollection).get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});
More reading can be found in the documentation Get Data Once

firestore.onSnapshot callback function is blocking render process

I am using Firestore's onSnapshot() method to listen to a collection of ~5,000 documents. In the callback passed to onSnapshot, I am taking the snap and formatting the docs into an array and object.
db.onSnapshot(snap => {
const list = [];
const ref = {};
for (let i = 0, size = snap.size; i < size; i++) {
const doc = snap.docs[i];
const id = doc.id;
const data = doc.data();
list.push(data);
ref[id] = { ...data };
}
// Save list and ref to redux store.
}
This process has been working well with smaller collections. However, now with this larger collection, when a user submits a new document to the collection, the "success" prompt that a user sees is blocked by the for loop.
To elaborate: When a user submits a new document, db.add(newDocument) is called. In the .then() we are re-rendering the page to show the user a confirmation message. At the same time though, the snapshot listener has detected a change in the collection and is now looping over all 5,000 documents again. This only takes a second or two but it produces a noticeable "lag" for the user.
How should this process be handled?
You should probably:
Preserve the value of ref for as long as the listener stays attached - don't recreate it each time.
Iterate snap.docChanges so you will only have to process those changes that will result in your prior ref contents to change. This will be indicated in the type of the change.
This should drastically reduce the number of objects being created and destroyed every time something changes.

Vue // this.$root.$off unsubscribes all instances of the component from the global event

I have a custom input validation component that I use in a form. Something like 15 instances of this component around the app. It has a beforeDestroy method in which I unsubscribe from global event called triggerGlobalValidation which triggers validation before I send request to server. As expected it's triggered only once inside this certain component.
There is a container with v-if parameter which contains one instance of the component. So when v-if="false" I expect this certain component to unsubscribe from event and get destroyed. It goes well accept for one thing: somehow this component unsubscribes ALL other instances of it from the triggerGlobalValidation event as well.
I've tested the behavior with v-show and it works as expected - all other instances keep subscribed, but since the v-show field is required for the form it's blocking validation even without being shown in the DOM. I also tested above mentioned components behavior by removing the this.$root.$off("triggerGlobalValidation") and it also works as expected + polluting the global root.
Vue documentation on $off method is saying:
If no arguments are provided, remove all event listeners;
If only the event is provided, remove all listeners for that event;
If both event and callback are given, remove the listener for that
specific callback only.
Is it possible to somehow mention in the callback, that this $off method shouldn't unsubscribe all of its instances from the event, but just this certain one being destroyed?
Check it out in codesandbox
As answered in the issue, you need to save the handler and pass it again to $off
mounted() {
this.fn = () => {
this.toggleWarning();
}
this.$root.$on("triggerChildComponents", this.fn);
},
beforeDestroy() {
this.$root.$off("triggerChildComponents", this.fn);
},

Firebase (Google) Cloud Functions - Debounce/Throttle database.onWrite() #AskFirebase

Scenario
I have documents stored for each user at path documents/${documentId}
Goal
I want to parse them and update the index for that document when it changes
Code
import Functions from 'firebase-functions'
export writeTrigger = Functions
.database
.ref('/document/{documentId}')
.onWrite(
async event => {
const data = event.data.val()
const { documentId } = event.params
// assume that updateIndex function exists
updateIndex(documentId, data)
}
)
Problem
This function gets called for every single letter being typed into the document
TLDR
What is the best way to throttle/debounce firebase cloud functions (database.onWrite) so that it isn't fired on each and every change?
Your function will get invoked for each and every change at or under the path you specify. There's currently no way to prevent this.
Instead of writing each and every change to the database, instead try batching up changes on the client and writing them out in bulk, or saving state periodically.
Alternatively, give the client some other way to indicate that it's time for the function to do work, maybe some field in the document and listen to only that field's changes. Here's one that just triggers when a field done is changed:
export writeTrigger = Functions
.database
.ref('/document/{documentId}/done')
.onWrite(...)
Just be sure to unset that value so that the client can indicate another set of changes should be processed.

Dynamically added rows onchange event javascript

Im using javascript and jsp for creating rows dynamically. When value in a particular cell changes, onchange event will be fired and the message will be given.
When an onchange event is used in java script, first i tried to pass the id directly,
element1.onchange=checkInputValue(element1.id);
onchange event fired even when cell gets created. Later when i changed to
element1.onchange = function(evt){ testInputElement(this.id); };
and inside "testInputElement", the onchange event is called and the function worked fine. What is the use of this? Why we need to pass the function inside the function?
Is there any other way?
TIA
The onchange property allows you to specify a function that should be executed whenever that element changes. In your first example you are assigning the return value of checkInputValue to onchange, which the element can't execute.
What you want to do is assign a function that should be executed whenver the elemnent changes, which you correctly do in the second example.
If you don't want to pass a new function to onchange, you could instead modify checkInputValue to accept a change event. The change event contains information about where the event originated, including the elemnt.
function handleOnChange(event) {
var id = event.target.id;
// do some stuff
}
// pass a reference to the function, rather than executing it.
// when element1 changes it will call handleOnChange, and pass an
// event object
element1.onchange = handleOnChange;

Categories