DOM is rendering before Firebase is complete - firebase

I am grabbing some data from Firebase to display on my webpage. While the text is loaded correctly, I keep getting errors in my console saying that I am loading undefined variables. This is important because I eventually want to be able to add an edit feature. I determined that the Firebase call is rendering after the DOM is loaded and that the data is set to null initially.
I've tried to use different lifecycle hooks but none of them wait till the Firebase call is complete. I am not that experienced with Javascript so I may be missing something simple.
created(){
db.collection('recipes').doc("Grilled Cheese").get()
.then((doc) => {
this.contentData = doc.data();
})
}
data() {
return {
sidebarData: null,
contentData: "",
}
},
I want to populate the contentData with the correct values before the DOM renders completely.

Knowing that you can't force the mounted hooks to be executed after the created hook ends ( the DOM render will not wait for your firebase response ) ... you need to add a conditional v-if on your template so only if the data is available ( not undefined ) the DOM will be rendered.
<template v-if="contentData">...</template>
NB : null and "" are defined data and that should not cause any rendering issues ... make sure doc.data() is not undefined

Related

Vue3 prop updates title attribute but not computed prop

I'm using an external library rendered using Vue3. It has the following component from a third part library [Edit: I realize the GitHub repo for that library is out of date, so updating with the actual code in my node_modules.]
<template>
<div class="socket" :class="className" :title="socket.name"></div>
</template>
<script>
import { defineComponent, computed } from "vue";
import { kebab } from "./utils";
export default defineComponent({
props: ["type", "socket"],
setup(props) {
const className = computed(() => {
return kebab([props.type, props.socket.name]);
});
return {
className
};
}
});
</script>
It renders based on a Socket object passed as a prop. When I updated the name property of the Socket, I see the title updated accordingly. However, the CSS/class does not update. I've tried $forceRefresh() on its parent, but this changes nothing.
Update: I was able to move the rendering code to my own repo, so I can now edit this component if needed.
Based on this updated code, it seems the issue is that the class is computed. Is there any way to force this to refresh?
The only time it does is when I reload the code (without refreshing the page) during vue-cli-service serve.
For reference, the | kebab filter is defined here:
Vue.filter('kebab', (str) => {
const replace = s => s.toLowerCase().replace(/ /g, '-');
return Array.isArray(str) ? str.map(replace) : replace(str);
});
Do filtered attributes update differently? I wouldn't think so.
I was also wondering if it could be a reactivity issue, and whether I needed to set the value using Vue.set, but as I understand it that's not necessary in Vue3, and it's also not consistent with the title properly updating.
Computed properties are reactive, however Vue does not expect you to mutate a prop object.
From the documentation:
Warning
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect the parent state and Vue is
unable to warn you against this. As a general rule, you should avoid
mutating any prop, including objects and arrays as doing so ignores
one-way data binding and may cause undesired results.
https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
I know that this says, that you should not mutate it in the child, but the general rule is, that you should not mutate properties at all, but instead create new object with the modified data.
In your case the computed function will look for changes in the properties itself, but not the members of the properties, that is why it is not updating.

Issues with the material table updating when using paginator

I've searched and I've searched and I've searched, I can't find an answer to this.
I am using Angular Firestore to fetch data (tried both snaphotChanges and valueChanges). The code works.
However, if the data changes on the backend, the material table row on the UI only refreshes automatically when either I am showing all the data or if I disable paging. However, when paging is enabled, the UI resorts to show that the data got updated, but it does not refresh the cell value until I move the cursor on the row to force a refresh. I don't know if it's a bug or if I am doing something wrong.
I made a video showing what happens: https://youtu.be/SN6EFc6Vqm8
// Have this in my service. It works
getContactsByLimit(limit) {
return this.afs
.collection("users")
.doc(this.user.currentUserId)
.collection("contacts", ref =>
ref
.orderBy("id", "desc")
.limit(limit)
)
.valueChanges();
}
// The paginator declaration
#ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
// This works
this.contactsSubscription = this.contactsvc
.getContactsSnapshotChangesByLimit(3)
.subscribe(contacts => {
// This works, I get new data.
this.dataSource.data = contacts;
});
// HTML template
<mat-paginator class="paginator"
[length]="15" // This breaks the refresh
[pageSize]="3"
[pageSizeOptions]="[2,3,5]"
(page)="pageEvent($event)"></mat-paginator>
// The automatic refresh does not work if I set
// the length to be > 3 in this case (unless I move
// the mouse on the table).
The UI should refresh with new data whether the length > pageSize or not.
Figured it out. I was using trackBy to track by ID. I added a timestamp field in my document and started tracking by timestamp.
<table [dataSource]="dataSource" [trackBy]="trackByTimestmamp" mat-table>
and
trackByTimestmamp(index, item) {
return item.timestamp;
}
This forces the material table to update the table when the timestamp changes.

Meteor: reactive: false not working

I am using Meteor with React and experiencing weird issue with reactive:false.
Here is what I am trying to do: The server has a collection 'Apps', and the client needs to get the 'Apps' based on what app-category the user chose. The first time the Apps are fetched, they should be sorted based on 'subscribeCount'. After that, the page should only re-render itself if the user choose another category and it should not re-render for 'subscribeCount' changes.
I tried to use reactive: false. However, I can't seem to stop the change of 'subscribeCount' from re-rendering the page.
Here is my code:
getMeteorData(){
const chosenApps = Apps.find({
categoryNames: {
$in: [this.state.chosenCategory]
}
},
{sort: {subscribeCount: -1}, reactive: false}
).fetch();
return {
chosenApps: chosenApps,
}
},
render(){
console.log(this.data.chosenApps);
return <div>
...
</div>
}
Here is why reactive:false didn't work for me:
I subscribed more than one collections in getMeteorData() method. Although I put reactive: false in one of the collections, when the other collections changes, the entire getMeteorData() method will be re-run, and that triggers all the collections to be fetched again.
To fix the problem, I moved the rest of the collections in a child component. And leave only the reactive: false collection in the current component.

How to change the value of a variable on click in meteor

In my meteor app I need to load an array of items corresponding to the item clicked.As I'm new to meteor, I'm held up here.Here is my code.
Template.templatename.events({
'click .showdiv' : function()
{
Template.templatename.vname = function () {
return Db.find();
}
}
Can I set the variable vname dynamically by this code ? This is not working for me.
I think you're misunderstanding the notion of reactivity. A reactive data source will cause any functions which depend on it (including helpers) to rerun when its value is changed, which seems to be the behavior you're looking for here. Instead, you're rewriting the helper function itself every time an item is clicked, which kind of defeats the object of Meteor's reactive data model. Session variables could help:
Template.templatename.events({
'click .showdiv' : function() {
Session.set('vname', Db.find());
}
});
Template.templatename.vname = function () {
return Session.get('vname');
}
If you use an {{#each vname}} block in the templatename template, it will automatically update with the results of the Db.find() query when a .showdiv is clicked. If all you want to do is show the result of that query regardless of whether a click has been registered it would be as simple as:
Template.templatename.vname = function () {
return Db.find();
}
Note that it's still not clear exactly what data you're trying to populate here since the query will return a cursor (which is fine, but you need to loop through it using {{#each ...}} - use findOne if you only want one item), and its contents aren't going to depend on anything intrinsic to the click event (like which .showdiv you clicked). In the former example it will however fail to show anything until the first click (after which you would have to reset with Session.set('vname', null) to stop it showing anything again).

How to update using angularFireCollection which items not exist previously

I have this parent node with text and I want to add a media into the parent node also if the text is changed will update as well.
{
parent: {
text: 'this is content'
}
}
The media value from a third-party callback. How to pass it to update()? I tried {media: callbackVal} but not working.
$scope.parent = angularFireCollection(firebaseRef.child('parent'));
$scope.parent.update(What_to_do_here, function(error){
//something...
});
UPDATE
Maybe my question is not clear enough.
In Firebase JS, we can do this to update or insert media into the node.
new Firebase(firebaseRef).update({ media: 'value'} );
How to do this in `angularFireCollection ?
Check out the annotated AngularFire source: http://angularfire.com/src/angularFire.html#section-38
It looks like AngularFireCollection.update() takes a key/item and a callback function. So you need to edit the entry you want to update directly in your AngularFireCollection, (e.g. $scope.parent.getByName('media').value = 'alligator scrimshaw') and then call $scope.parent.update($scope.parent.getByName('media'), function(err) { ... }).
NOTE: One rather confusing thing (which maybe I am misunderstanding) is that there doesn't seem to be a way to add data to an AngularFireCollection by id. So if media doesn't already exist in your Firebase, $scope.parent.getByName('media') will return undefined. For this reason, if you don't need explicit syncing, and implicit syncing is fine, I would use an AngularFire object instead of an AngularFireCollection.

Resources