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

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.

Related

Svelte value not reactive

I've a variable styles in the Svelte store that I would like to update:
export const styles = writable();
Now in my mainframe.svelte file, I've an EventListener that listens to a click and updates the store value as follow:
document.addEventListener("click", (e) => {
styles.update(() => getComputedStyle(e.target));
});
And in my spacing.svelte file, I want to console.log when the value changes, but it's not at all updating and stuck at undefined:
$: console.log(get(styles));
Now if I use setInterval then it is working and updating the values as per clicks, so it's definitely not the code but the problem is with reactivity itself:
setInterval(() => {
console.log(get(styles));
}, 1000);
What am I doing wrong here? Why the value is not changing automatically on clicks but setInterval seems to work?
This is not working because of how reactivity works.
In your code $: console.log(get(styles)); you have the following parts:
$: this marks the line as reactive, it will run again when any variable (or function) used on that line changes.
console.log, this never changes
get, this is a helper function from the stores, it never changes
styles, this is the store it never changes (the value does, but not the store itself)
conclusion: this line is run once and then never again.
the solution is simple, instead of doing get which is used to get the current value of a store once (and usually only used in script files where reactivity doesn't work), you can simply use the value itself:
$: console.log($styles);

Change a JavaScript export based on event

I'm using react-admin. I have firebase.js, which exports a data provider and an auth provider. Right now firebase.js looks something like
export const authProvider = MyCustomAuthProvider(...)
export const dataProvider = FirebaseDataProvider(..., {rootRef: "users/[authProvider.get~this~user().email]")
But I'd like the dataProvider imported to change when the user logs in / out (the reason being that the data provider has as its root collection at 'users/[user email]'). That's actually why I stopped using the FirebaseAuthProvider blackbox, because I figured I could use the login/logout functions to trigger changing the dataProvider.
What's the best way to accomplish this? Just declare the dataProvider using
let dataProvider = null and every time a user logs in/out, set that variable. And then add a function to the auth provider that returns that variable? There seems to be a more elegant way, but I'm not as experienced in JavaScript unfortunately.
Also for reference, the reason I'm taking this approach instead of just creating a dataProvider with rootRef 'users' and then accessing the correct collection / document when I want to view or modify data is because FirebaseDataProvider is a bit of black box and doesn't allow me to do that (unless I'm missing something). If I could get this all to work with FirebaseDataProvider, it has saved me a ton of time so that would be great.

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

Unique storage for every function call inside React HOC

I want to create a helper that generates some data and saves it in some variable and on the next execution, it should use the memoized value for calculation.
Basically it's a helper for the High Order wrapper. It means that the storage should be created for every HOC but it shouldn't be re-created on the next re-render.
Now it looks like:
pseudo code
var storage; // I want to create this storage for every HOC only once.
function createDynamicStyles(theme, stylesCreator, status) {
// create a styles registry only once. This function can be re-called by the same HOC
// on each re-render so I want to use the memoized registry.
if (!storage) {
storage = stylesCreator(theme);
};
return storage[status];
}
const styleCreator = theme => ({
disabled: { color: theme.disabled },
success: { color: theme.success }
})
const Component_1 = componentHOC((props) => {
const { theme, status } = props;
// I'd like to keep this helper call as simple as possible. It already has 3 arguments.
const finalStyle = createDynamicStyles(theme, stylesCreator, status);
})(AwesomeComponent)
// these props can be changed during runtime
<Component_1 disabled={false} success={true} />
The functionality flow of this helper can be divided into 2 steps.
1) The first HOC call. It creates the styles based on the theme and saves them in the storage
2) Next Re-render of the HOC. It should fetch the previously created styles and return memoized value. This value should be unique for each HOC.
The problem is that the same helper can be used for other Components as well and it means that we can't use the same storage because it will be overwritten but the 'latest' HOC.
The possible ways how to solve it:
1) Create a class that will contain storage itself and creates a new Instance for each HOC.
To be honest, I'd like to avoid it because it looks too complicated for me in this case.
2) Create some Shared Registry and pass the UUID for every HOC.
It'd be nice but I don't know how to automatically do it. I don't want to manually pass the UUID on each HOC. I'd like to have this functionality under the hood to keep HOC calls, lightweight.
I was thinking about the new Map, and saving the created styles as Key-Value pair but it simply doesn't work as we don't have the generated KEY reference in the HOC. So we can't use it as a key.
Is it possible to do such a thing in the case of plain functions only?
Maybe I missed some other interesting variants.
Thanks for any help and suggestion.
Kind Regards.

Register Firebase Listener Without Calling It?

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'

Categories