Firebase realtime database get parent node value .val() - javascript

I have the below code that looks for a specific update in my /users object tree.
This works fine, but seems a bit redundant, since I already have a reference to a child of the user node that I want.
exports.onUserKeyHelloCreate = functions
.region('europe-west1')
.database
.ref('users/{userId}/hello')
.onCreate(async (snapshot, context) => {
const userId = context.params.userId
let userData = null
await database.ref(`users/${userId}`).once('value', (userSnapshot)=>{
userData = userSnapshot.val()
})
console.log('userData', userData)
Is there a more elegant way to access the entire user node that does not require an extra roundtrip to .once('value')?
Kind regards /K

If you need the entire contents of your database at "users/${userId}", what you're doing now is really the only way to do it.
Though you shouldn't really combine use of the callback along with the promise returned by once(). It's sufficient to just use the promise.
const snapshot = await database.ref(`users/${userId}`).once('value');
const userData = snapshot.val();
console.log('userData', userData)

Related

Bulk way to determine of all paths, which one exists (Possibly in one API Call)

I have an array of strings and want to check which of all those documents exist in my Firestore in two ways:
I am using Node Admin SDK:
import Server from '../../../../firebase/firebase_server_exports';
export const databaseEntryExists = async (paths: string[]): Promise<string> => {
const promises = paths.map(
(element) =>
new Promise((resolve) => {
Server.db
.doc(element)
.get()
.then((doc) => resolve(doc.exists ? element : ''));
})
);
const results = await Promise.all(promises);
return results.includes(true) ? paths[results.indexOf(true)] : '';
};
Though this method works nicely, if I have 100 paths, I will be making 100 API calls which doesn't sound a good deal to me. Can someone suggest any Fierstore utility function or something like batch document fetching in order to reduce this to one API call?
In the Node.js SDK, you can create DocumentReference objects for all IDs and then use getAll() to get them all in one go. See the documentation for getAll() for an example.
This call does not exist in the client-side SDKs though, so the closest you can get there is to combine up to 10 IDs in a single call by querying on FieldPath.documentId() and using the in operator. There is nothing to request

How to enable persistence on reactfire?

I'd like to implement Firestore offline persistence on my PWA React app using the reactfire library.
const firestore = useFirestore().enablePersistence();
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
but running the code i get an error:
FirebaseError: Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
This component is wrapped inside a <Suspense> as mentioned in the documentation
That database read is the only one that i make in the entire app, how can i solve?
Edit.
Using the example that #Ajordat gave, I've imported the preloadFirestore function inside the App component I do get an error:
"Cannot read property 'name' of undefined".
Whereas adapting (because I cannot use hooks inside the fetch function)
the example from #DougStevenson: I've imported useFirestore function in the App component (in order to get the Firestore object) to enable persistence, and then importing it (useFirestore) into my component in order to retrieve the data, but now, I get the same error as before,
Firestore has already been started and persistence can no longer be enabled.
Edit 2:
I've tried to enablePersistence without errors, thank guys, this is my approach, let me know if it is the best:
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
And in my custom component:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let document = useFirestoreDocDataOnce(docRef);
console.log(document)
But now I do have a problem, when I log the document, the data are not emitted instantly, yeah I know that it is an asynchronous operation, but the component is wrapped inside a <Suspense>, in this way:
<Suspense fallback={<div>Loading</div>}>
<FoodComponent foodName={"Milkshake"} />
</Suspense>
But I don't see the loading text before the component is actually rendered.
Does the suspense fragment show the fallback component only while is loading the function (useFirestore) and not the actual data?
Well, I've solved, have to destructure the data, doing like that:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document)
On other JavaScript libraries for Firestore, enablePersistence() returns a promise. That means it will complete some time in the future, with no guarantees how long it will take. If you're executing the query immediately after you call enablePersistence(), without waiting for the returned promise to become fulfilled, then you will see this error message. That's because the query "beats" the persistence layer and effectively executes first.
You will have to figure out how to use that promise to wait until it's OK to make that query with persistence enabled. For example:
seFirestore().enablePersistence()
.then(() => {
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
})
.catch(error => {
console.error("enablePersistence failed", error);
})
Notice how the query will complete only after the persistence is fully enabled.
Thanks for the suggestion guys #DougStevenson and #Ajordat
In app component:
import { useFirestore } from "reactfire"
...
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
In your custom component, where you want to use Firestore:
import { useFirestore, useFirestoreDocData /* or what you want to use */ } from "reactfire"
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document);

Cloud functions cron job: How to manage the nested collection's data

I want to trigger the scheduled task to the nested collection data using cloud functions cron job.
What I want to achieve is, if the timestamp value in "limit" field is later than the current time, "status" field will be added with the value "expired" in the nested collection.
The parent collection name is "OrdersUser" and the child collection name is "Orders".
And I want to manage the documents in "Orders" collection.
The scheduled task runs every one minute.
After I deployed, I got an error in the firebase console.
id is not defined.
I thought I could use wild card in the cloud functions, so I used {id} to refer the nested documents. But I couldn't.
I want to query all the data in ”Orders” collection.
How can I fix this issue?
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp()
const db = admin.firestore()
const ref = functions.firestore
exports.statusOrdersUser = functions.runWith( { memory: '2GB' }).pubsub.schedule('* * * * *').onRun((context) => {
// Consistent timestamp
const now = admin.firestore.Timestamp.now();
// Query all documents ready to perform
//id is not defined.
const queryOrdersUser = db.collection('OrdersUser').doc({id}).collection('Orders').where('limit', '<=', now)
return queryOrdersUser.get()
.then(querySnapshot => {
if (querySnapshot.empty) {
return null;
} else {
const promises = []
querySnapshot.forEach(doc => {
promises.push(doc.ref.update({ status: 'expired' }));
});
return Promise.all(promises);
}
});
})
Firestore does not support wildcard operations for document IDs. If you want to query a subcollection, you need to provide the specific ID of the document where it is nested. It's not clear how your function is supposed to know exactly which subcollection to query, since it doesn't receive any arguments or have any context.
If you want to query all of the subcollections named "Order", no matter where they are nested anywhere in your database, you can use a collection group query for that.

Get array of objects from real time data snapshot - Cloud Firestore

I'm trying to fetch real time data from Cloud Firestore using the below code.
export const getRealTimeData = () =>
db
.collection('posts')
.onSnapshot(
(querySnapshot) => {
const posts: any = [];
querySnapshot.forEach((doc) =>
posts.push(Object.assign({
id: doc.id
}, doc.data()))
);
},
);
};
And, I want to use the resultant array to display the data on UI. When I'm doing this, the resultant array is a function but not the actual array of data.
const posts = getRealTimeData();
Here's what I get when I log posts
function () {
i.kT(), o.al(s);
}
Could anyone please point where I went wrong?
Realtime listeners added with onSnapshot() are not compatible with returning values from function calls. That's because they continue to generate new results over time, and would never really "return" anything once. You should abandon the idea of making a synhronous getter type function in this case - they just don't work for what you're trying to do.
Ideally, you would use an architecture like Redux to manage the updates as they become available. Your realtime listener would dispatch query updates to a store, and your component would subscribe to that store that to receive those updates.
If you don't want to use Redux (which is too bad - you really should for this sort of thing), then you should wrap your query inside a useEffect hook, then have your listener set a state hook variable so your component can receive the updates.

Firebase child_added for new items only

I am using Firebase and Node with Redux. I am loading all objects from a key as follows.
firebaseDb.child('invites').on('child_added', snapshot => {
})
The idea behind this method is that we get a payload from the database and only use one action to updated my local data stores via the Reducers.
Next, I need to listen for any NEW or UPDATED children of the key invite.
The problem now, however, is that the child_added event triggers for all existing keys, as well as newly added ones. I do not want this behaviour, I only require new keys, as I have the existing data retrieved.
I am aware that child_added is typically used for this type of operation, however, i wish to reduce the number of actions fired, and renders triggered as a result.
What would be the best pattern to achieve this goal?
Thanks,
Although the limit method is pretty good and efficient, but you still need to add a check to the child_added for the last item that will be grabbed. Also I don't know if it's still the case, but you might get "old" events from previously deleted items, so you might need to watch at for this too.
Other solutions would be to either:
Use a boolean that will prevent old added objects to call the callback
let newItems = false
firebaseDb.child('invites').on('child_added', snapshot => {
if (!newItems) { return }
// do
})
firebaseDb.child('invites').once('value', () => {
newItems = true
})
The disadvantage of this method is that it would imply getting events that will do nothing but still if you have a big initial list might be problematic.
Or if you have a timestamp on your invites, do something like
firebaseDb.child('invites')
.orderByChild('timestamp')
.startAt(Date.now())
.on('child_added', snapshot => {
// do
})
I have solved the problem using the following method.
firebaseDb.child('invites').limitToLast(1).on('child_added', cb)
firebaseDb.child('invites').on('child_changed', cb)
limitToLast(1) gets the last child object of invites, and then listens for any new ones, passing a snapshot object to the cb callback.
child_changed listens for any child update to invites, passing a snapshot to the cb
I solved this by ignoring child_added all together, and using just child_changed. The way I did this was to perform an update() on any items i needed to handle after pushing them to the database. This solution will depend on your needs, but one example is to update a timestamp key whenever you want the event triggered. For example:
var newObj = { ... }
// push the new item with no events
fb.push(newObj)
// update a timestamp key on the item to trigger child_changed
fb.update({ updated: yourTimeStamp })
there was also another solution:
get the number of children and extract that value:
and it's working.
var ref = firebaseDb.child('invites')
ref.once('value').then((dataSnapshot) => {
return dataSnapshot.numChildren()
}).then((count) =>{
ref .on('child_added', (child) => {
if(count>0){
count--
return
}
console.log("child really added")
});
});
If your document keys are time based (unix epoch, ISO8601 or the firebase 'push' keys), this approach, similar to the second approach #balthazar proposed, worked well for us:
const maxDataPoints = 100;
const ref = firebase.database().ref("someKey").orderByKey();
// load the initial data, up to whatever max rows we want
const initialData = await ref.limitToLast(maxDataPoints).once("value")
// get the last key of the data we retrieved
const lastDataPoint = initialDataTimebasedKeys.length > 0 ? initialDataTimebasedKeys[initialDataTimebasedKeys.length - 1].toString() : "0"
// start listening for additions past this point...
// this works because we're fetching ordered by key
// and the key is timebased
const subscriptionRef = ref.startAt(lastDataPoint + "0");
const listener = subscriptionRef.on("child_added", async (snapshot) => {
// do something here
});

Categories

Resources