Reading data after writing to it in Firebase - javascript

I am trying to navigate to a specific ID after creating an item in my realtime database.
The documentation offers a solution but it is not working as shown.
After using the push().set() method on my db ref I tried creating a callback function that would get the specific ID created using the push.().set() method and then navigate with that ID.
db.ref.push().set({
...
}).then(function(){
router.navigate(ref/ + db.ref.key /)
})
After creating a new object at that ref point I want to use the ID created by firebase to navigate to a specific route using that ID.

The issue is that db.ref.push() returns a new Reference object with the unique id to it. You're not getting the value of that ID. You can refactor things so it would be something like:
let newref = db.ref.push();
newref.set({
...
})
.then( () => {
// You can use "newref" here
// The last part of the full reference path is in "key"
let id = newref.key;
// Do something with the id as part of your route
});

set
set(value, onComplete) => returns firebase.Promise containing void
So you are absolutely correct about that you should be able to see the operation as resolved with a callback however the promise will not give you any key back.
You should however be able to chain this event:
db.ref.push().set({
...
}).getKey().then(data => console.log(data))

Related

RTKQ - Select data without hooks

I'm trying to select cached data from RTKQ without using the auto-generated query hook, but I'm having trouble understanding the docs
const result = api.endpoints.getPosts.select()(state)
const { data, status, error } = result
This is how the docs describe how to access the data, but I can't find any references on how to inject the state object "select()(state)".
I can't figure out how to access the data if I only call the select?
api.endpoints.getPosts.select()
Can someone explain me the difference between "select()" and "select()(state)"
Or what is the optimal solution to access the cached data from RTKQ?
The result of api.endpoints.getPosts.select() is a selector function for the result of using the "getPosts" endpoint without arguments.
Similarly, result of api.endpoints.getPosts.select({ page: 5 }) is a selector function for the result of using the "getPosts" endpoint the argument { page: 5 }.
A selector function is then called as selector(state) or passed into useSelector(selector).
If you write that altogether, you end up with api.endpoints.getPosts.select()(state).
#phry
Thank you for your answer! I'm not 100% sure I understood your answer. But it pointed me in a direction that enabled me to get the data.
I ended up creating a selector like the docs.
export const selectUser = (state) => userApi.endpoints.getUser.select()(state);
and in my function, I referenced it with getting the exported store from configureStore() method
const { data } = selectUser(store.getState());
But I'm not sure if this is the intended way to do it.

How to get the last document from a VueFire query

Getting frustrated to solve this since I am no JS expert. 😢
I am using Firestore as a database and VuexFire to bind the data to VueX state, like so.
getLeads: firestoreAction(async ({
bindFirestoreRef
}) => {
// return the promise returned by `bindFirestoreRef`
return bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30))
}),
It gets the first 30 results and then i want to implement an infinite scroll feature to run a function every time the scroll reaches the bottom and fetch more data and bind to the same state. In Firestore pagination require passing a query cursor of the last fetched document as a reference
Below from firebase document, with vanilla JS
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
});
since I use VuexFire to bind the data to state, I dont see an option to get the snapshot of the last document fetched by VuexFire (lastVisible from the above code), in order to pass it to the next query.
Any help will be highly appreciated. 🙏🏽
Lets say I have a collection of Customer records and i am displaying the first 5 ordered by last updated.
The query is
getLeads: firestoreAction(({ commit, bindFirestoreRef
}) => {
bindFirestoreRef('Leads', db.collection('leads')
.orderBy('updated.date', 'desc').limit(5)).then(documents => {
commit('POPULATE_TESTLEADS', documents);
commit('LAST_DOC', documents[documents.length - 1]);
});
}),
I am saving both the results and the lastdoc in the state, looping and showing the names, like so:
Nakheel
Emaar Group
Yapi Kredi Inc
Cairo Amman Bank
Arab Jordan Investment Bank LLC
I then call again with the last doc as query cursor and expect the next 5 docs to return from firebase, like so
moreLeadLeads: firestoreAction(({ state, bindFirestoreRef
}) => {
bindFirestoreRef('testLeads', db.collection('leads')
.orderBy('updated.date', 'desc')
.startAfter(state.lastDoc).limit(5))
}),
But I get the same 5 results as above from firestore. What am I doing wrong? :(
Internally VueFire and VuexFire use a serializer function that maps each Document returned by RTDB or Firestore into the data objects that are bound to the final component or Vuex store state.
The default serializer is implemented by the function createSnapshot that is part of the vuefire-core library:
/**
* #param {firebase.firestore.DocumentSnapshot} doc
* #return {DocumentData}
*/
export function createSnapshot (doc) {
// defaults everything to false, so no need to set
return Object.defineProperty(doc.data(), 'id', {
value: doc.id
})
}
As you can see it returns only doc.data() (with id added) and discards the doc object. However when implementing Firestore pagination via query.startAfter(doc) we need the original doc object. The good news is that VueFire and VuexFire APIs allow us to replace the serializer with our own that can preserve the doc object like so:
const serialize = (doc: firestore.DocumentSnapshot) => {
const data = doc.data();
Object.defineProperty(data, 'id', { value: doc.id });
Object.defineProperty(data, '_doc', { value: doc });
return data;
}
We can configure our new VuexFire serializer either globally via plugin options or per binding via binding options.
// Globally defined
Vue.use(firestorePlugin, { serialize });
// OR per binding
bindFirebaseRef('todos', db.ref('todos'), { serialize } )
For VuexFire, we can now get access to the first document as state.todos[0]._doc or last document state.todos[state.todos.length-1]._doc and use them to implement pagination queries for collections or "get next" & "get previous" queries that bind single documents (essential when your base query has multi-field sorting).
NOTE: Because _doc and id are non-enumerable properties, they won't appear on component or store objects in Vue DevTools.
From the VueFire documentation on binding data and using it, the $bind method (which I assume your bindFirestoreRef wraps) returns a promise with the result (as well as binding it to this). From that documentation:
this.$bind('documents', documents.where('creator', '==', this.id)).then(documents => {
// documents will point to the same property declared in data:
// this.documents === documents
})
So you should be able to do the same, and then get the document from the array with something like:
bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30)).then(documents => {
this.lastDoc = documents[documents.length - 1];
})

How to push to Observable of Array in Angular 4? RxJS

I have a property on my service class as so:
articles: Observable<Article[]>;
It is populated by a getArticles() function using the standard http.get().map() solution.
How can I manually push a new article in to this array; One that is not yet persisted and so not part of the http get?
My scenario is, you create a new Article, and before it is saved I would like the Article[] array to have this new one pushed to it so it shows up in my list of articles.
Further more, This service is shared between 2 components, If component A consumes the service using ng OnInit() and binds the result to a repeating section *ngFor, will updating the service array from component B simultaneously update the results in components A's ngFor section? Or must I update the view manually?
Many Thanks,
Simon
As you said in comments, I'd use a Subject.
The advantage of keeping articles observable rather than storing as an array is that http takes time, so you can subscribe and wait for results. Plus both components get any updates.
// Mock http
const http = {
get: (url) => Rx.Observable.of(['article1', 'article2'])
}
const articles = new Rx.Subject();
const fetch = () => {
return http.get('myUrl').map(x => x).do(data => articles.next(data))
}
const add = (article) => {
articles.take(1).subscribe(current => {
current.push(article);
articles.next(current);
})
}
// Subscribe to
articles.subscribe(console.log)
// Action
fetch().subscribe(
add('article3')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
Instead of storing the whole observable, you probably want to just store the article array, like
articles: Article[]
fetch() {
this.get(url).map(...).subscribe(articles => this.articles)
}
Then you can manipulate the articles list using standard array manipulation methods.
If you store the observable, it will re-run the http call every time you subscribe to it (or render it using | async) which is definitely not what you want.
But for the sake of completeness: if you do have an Observable of an array you want to add items to, you could use the map operator on it to add a specified item to it, e.g.
observable.map(previousArray => previousArray.concat(itemtToBeAdded))
ex from angular 4 book ng-book
Subject<Array<String>> example = new Subject<Array<String>>();
push(newvalue:String):void
{
example.next((currentarray:String[]) : String[] => {
return currentarray.concat(newValue);
})
}
what the following says in example.next is take the current array value Stored in the observable and concat a new value onto it and emit the new array value to subscribers. It is a lambda expression.I think this only works with subject observables because they hold unto the last value stored in their method subject.getValue();

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
});

Best approach to listen to a dynamic array of refs generated for Firebase?

Here's the scenario: I get an initial payload from Firebase (eg. 5 objects) from firebaseRef_1 (using a .once callback). I then transform each received object into a new Firebase ref. That newly generated ref looks like this:
"data/ccccyyyyotKKiWC2xaV1WZ7H3/things/-KgXo121225H9_Nks1O"
If for example I have 5 incoming objects on that .once call, then I'll create 5 refs (see code snippet below).
Once I've created/generated the ref(s) I want to:
a) Create a .on listener to each of those 5 refs.
b) If (lets say) some time later 1 'row' of new data arrives to firebaseRef_1, I want to generate a new ref, and then create a listener - but now, just for that newly added item. Otherwise if I try to loop through all of the (now 6) elements Firebase throws a promise error telling me that the original 5 elements (refs) already have a listener attached.
The reason that I was thinking to use a .once call initially, is so that I can get the whole payload "all at .once" (pun intended!) which makes populating a ListView (react native) faster. I tried using a .child_added approach but on the initial load (of those 5 rows) the .child_added gets called 5 times and so you can see the rows appear one by one in the ListView which I dont like.
How should I best structure this in code to achieve what I've described above? Should I just have a .once call like this:
generateTheRefs() {
firebase.database()
.ref('data/' + firebaseUID + '/initialPath')
.once('value', (snapshot) => {
snapshot.forEach(childSnapshot => {
var childData = childSnapshot.val();
let path = `data/${childData.item1}/things/${childData.item2}`;
//now create a .on listener
firebase.database()
.ref(path)
.on('value', (snapshot) => {
//....
});
});
});
}
You could:
Read the entire initial set with once(), and display that. Store it in a variable somewhere.
Then immediately register a listener with on('child_added'), and only act on things you did not previously collect from once() as you initially stored it.
It sounds like what you are after is to use all of the results at the same time so they don't dribble in over time. You can use an array of promises and Promise.all() to wait for them all to arrive before using them, something like:
function generateTheRefs() {
firebase.database()
.ref('data/' + firebaseUID + '/initialPath')
.once('value', (snapshot) => {
let promises = [];
snapshot.forEach(childSnapshot => {
var childData = childSnapshot.val();
let path = `data/${childData.item1}/things/${childData.item2}`;
//now create a .on listener
promises.push(firebase.database()
.ref(path)
.once('value'));
});
Promise.all(promises).then(snaps => {
snaps.forEach(snap => {
// ...
console.log(snap.val());
})
});
});
}

Categories

Resources