Meteor: reactive: false not working - meteor

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.

Related

DOM is rendering before Firebase is complete

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

VueJs child component props not updating instantly

I have a parent/child component setup where the parent is loading data from the server and passing it down to children via props. In the child I would like to instantiate a jQuery calendar with some of the data it receives from the parent.
In order to wait for the data before setting up the calendar, I broadcast an event in the parent that I have an event listener setup for in the child.
The listener is being fired in the child but if I this.$log('theProp'), it's undefined. However, if I inspect the components with the VueJs devtools, the parent/child relationship is there and the child has received the prop in the meantime.
The prop is defined on the child as a dynamic prop :the-prop="theProp". Since the child does receive the prop in the end, I'm assuming my setup is correct but there seems to be some sort of delay. The parent sets the props in the return function of the ajax call and again: it's working, just with a slight delay it seems.
I also tried registering a watch listener on the prop in the child so I could setup the calendar then and be sure that the prop is there. However, the watch listener fires, but this.$log('theProp') is still undefined.
If I pass the data along with the the broadcast call, like this.$broadcast('dataLoaded', theData) the child receives it just fine. But it seems wrong to do it that way as I'm basically building my own prop handler.
I'm not posting any code because the components are rather large and the VueJs devtools are telling me the parent/child situation is working.
Am I missing some information? Is there a slight delay between setting a value in the parent and the child receiving it? What would be the proper way to wait for parent data in the child?
Normally, when you're just rendering the data out into the template, the timing doesn't matter so much since the data is bound to the template. But in this case, I really need the data to be there to setup the calendar or it will be wrong.
Thanks.
edit 1: here's a jsfiddle: https://jsfiddle.net/dr3djo0u/1/
It seems to confirm that the data is not available immediately after the broadcast. However, the watcher does work, though I could almost swear that sometimes this.$log('someData') returned undefined when I setup that testcase.
But I guess my problem might be somewhere else, I'll have a look tonight, don't have the project with me right now.
edit 2: did some more tests. My problem was that a) event listeners do not seem to receive the data instantly and b) I was also trying to init the calendar in the route.data callback if someData was already around (e.g. when coming from parent), but that route callback is called before the component is ready, so it wasn't working there either.
My solution is now this:
// works when the child route is loaded directly and parent finishes loading someData
watch: {
someData() {
this.initCalendar();
}
},
// works when navigating from parent (data already loaded)
ready() {
if (this.someData && this.someData.length) {
this.initCalendar()
}
}
As far as I know, you should not need events to pass data from parent to child.
All you need is, in the child component: props: ['theProp']
And when using the child component in the parent: <child :theProp="someData"></child>
Now, wherever in the parent you change someData, the child component will react accordingly.
You don't need events, you don't need "watch", you don't need "ready".
For example: after an AJAX call, in the parent's "ready", you load some data:
// at the parent component
data: function () {
return {
someData: {}
}
},
ready: function () {
var vm = this;
$.get(url, function(response) {
vm.someData = response;
});
}
Now, you do not need anything else to pass the data to the child. It is already in the child as theProp!
What you really need to do is to have, in the child, something which reacts to data changes on its own theProp property.
Either in the interface:
<div v-if="theProp.id > 0">
Loaded!
</div>
Or in JavaScript code:
// at the child component
computed: {
// using a computed property based on theProp's value
awesomeDate: function() {
if (!this.theProp || (this.theProp.length === 0)) {
return false;
}
if (!this.initialized) {
this.initCalendar();
}
return this.theProp.someThing;
}
}
Update 1
You can also, in the parent, render the child conditionally:
<child v-if="dataLoaded" :theProp="someData"></child>
Only set dataLoaded to true when the data is available.
Update 2
Or maybe your issue is related to a change detection caveat
Maybe you're creating a new property in an object...
vm.someObject.someProperty = someValue
...when you should do...
vm.$set('someObject.someProperty', someValue)
...among other "caveats".
Update 3
In VueJS 2 you are not restricted to templates. You can use a render function and code the most complex rendering logic you want.
Update 4 (regarding OP's edit 2)
Maybe you can drop ready and use immediate option, so your initialization is in a single place:
watch: {
someData: {
handler: function (someData) {
// check someData and eventually call
this.initCalendar();
},
immediate: true
}
}
It's because tricky behavior in Vue Parent and Child lifecycle hooks.
Usually parent component fire created() hook and then mount() hook, but when there are child components it's not exactly that way: Parent fires created() and then his childs fire created(), then mount() and only after child's mount() hooks are loaded, parent loads his mount() as explained here. And that's why the prop in child component isn't loaded.
Use mounted() hook instead created()
like that https://jsfiddle.net/stanimirsp5/xnwcvL59/1/
Vue 3
Ok so I've spent like 1.5h trying to find out how to pass prop from parent to child:
Child
<!-- Template -->
<template>
<input type="hidden" name="_csrf_token" :value="csrfToken">
<span>
{{ csrfToken }}
</span>
</template>
<!-- Script -->
<script>
export default {
props: [
"csrfToken"
]
}
</script>
Parent
<!-- Template -->
<template>
<form #submit.prevent="submitTestMailForm" v-bind:action="formActionUrl" ref="form" method="POST">
...
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
...
</form>
</template>
<!-- Script -->
<script>
...
export default {
data(){
return {
...
csrfToken : "",
}
},
methods: {
/**
* #description will handle submission of the form
*/
submitTestMailForm(){
let csrfRequestPromise = this.getCsrfToken();
let ajaxFormData = {
receiver : this.emailInput,
messageTitle : this.titleInput,
messageBody : this.bodyTextArea,
_csrf_token : this.csrfToken,
};
csrfRequestPromise.then( (response) => {
let csrfTokenResponseDto = CsrfTokenResponseDto.fromAxiosResponse(response);
this.csrfToken = csrfTokenResponseDto.csrToken;
this.axios({
method : "POST",
url : SymfonyRoutes.SEND_TEST_MAIL,
data : ajaxFormData,
}).then( (response) => {
// handle with some popover
})
});
},
/**
* #description will return the csrf token which is required upon submitting the form (Internal Symfony Validation Logic)
*/
getCsrfToken(){
...
return promise;
}
},
components: {
CsrfTokenInputComponent
}
}
</script>
Long story short
This is how You need to pass down the prop to child
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
NOT like this
<CsrfTokenInputComponent csrf-token="csrfToken"/>
Even if my IDE keep me telling me yeap i can navigate with that prop to child - vue could not bind it.
solution (testing ok)
In child component just using the props data, no need to re-assignment props's values to data, it will be cause update bug!
vue child component props update bug & solution
https://forum.vuejs.org/t/child-component-is-not-updated-when-parent-component-model-changes/18283?u=xgqfrms
The problem is not how to pass data with props, but rather how to do two things at almost the same time.
I have an user account component that can edit users (with an user id) and add users (without id).
A child component shows checkboxes for user<->company assignments, and needs the user id to prepare API calls when the user account is saved.
It is important that the child component shows before saving the user account, so that things can be selected before the user is saved and gets an id.
So it has no user id at first: the id is passed to the child component as 'null'.
It updates when the user is stored and gets an id.
But at this point, it takes a very short time for the child to get the new id into its model.
If you call a function in the child component that relies on data that was just changing, it might happen that the function executes before the data is updated.
For cases like this, nextTick() is your friend.
import { nextTick } from 'vue';
...
saveAccount() {
axios.post(URL, this.userModel).then((result)) {
// our model gets an id when persisted
this.userModel.id=result.data.id;
nextTick( () => {
this.$refs.childComponent.doSomething();
});
}
}

Setting {reactive: false} when searching a collection in Meteor still updates the template

I've got a news feed idea I'm trying out in Meteor, but I'm having issues with making the damn thing behave :) I want it to load up the news feed on page load / refresh, but not when the data changes. I found in the documentation that adding {reactive: false} to the find method of a collection should make it stick to the results generated on render, but it doesn't seem to work for me. Meteor keeps updating the template right away.
Here's the code I've got:
On the server side:
Meteor.publish("newsfeed", function () {
return Newsfeed.find({});
});
On the client side:
Meteor.subscribe('newsfeed');
Template.feed.feed_data = function() {
var feed = Newsfeed.find({}, {
sort: {updated_time: -1},
limit: 10,
reactive: false
});
return feed;
};
In the template:
<template name="feed">
<div id="feed-wrapper">
<ul>
{{#each feed_data}}
<li>
<div class="message">{{message}}</div>
</li>
{{/each}}
</ul>
</div>
</template>
If I then run Newsfeed.update({_id: 'some_random_id'}, {$set: {date_created: 'some_random_date'}}) in Dev Tools, the template updates the sorting of my news feed and changes it.
How do I make it not do that? :D
This is arguably a bug in Meteor. Passing reactive: false means that minimongo itself doesn't set up some code to say "observe and if it changes, invalidate". But #each has its own separate observeChanges call which uses the observe callbacks directly (not a reactive "invalidate and recalculate") to update the list. Probably we should not do this if the cursor has reactive: false on it. Track this in https://github.com/meteor/meteor/issues/771 !
Thats a bit odd, it ought to work. You could also use preserve:
Try adding this line in your client js
Template.feed.preserve(['#feed-wrapper']);
Btw is the template name="feed" in another template? Does this template have any reactive variables in it?

Client-side-only reactivity with Meteor?

I have a collection published on the server and auto-subscribed on the client. I'd like to set the 'selected' item on the session and have the template update to display only the selected item, but it seems this can only be done with a roundtrip to the server (which is totally unnecessary).
Common:
var Missions = new Meteor.Collection('missions');
Client:
Template.missionList.missions = function() {
var currMission = Session.get('selectedMission');
var searchMission = {};
if(currMission)
{
searchMission['_id'] = currMission;
}
return Missions.find(searchMission);
};
Template.missionList.events({
'click div.mission': function (e, t) {
Session.set('selectedMission',
this._id == Session.get('selectedMission') ? null : this._id
);
}
});
Template.mission.isSelected = function() {
return this._id == Session.get('selectedMission');
};
Meteor.autosubscribe(function () {
Meteor.subscribe("missions");
});
Server:
Meteor.publish('missions', function() {
// there are really some filters here, but removed for simplicity
return Missions.find();
});
Template:
<template name="missionList">
<div class="missionList">
{{#each missions}}
{{> mission}}
{{/each}}
</div>
</template>
<template name="mission">
<div class="mission{{#if isSelected}} selected{{/if}}">details</div>
</template>
My requirement is for the Missions.find() in Template.missionList.missions to filter the client-side cached results, rather than to re-request from the server, but I can't seem to find a flag or settings to allow me to tell minimongo to only use the currently available data.
I'm also not entirely sure if this is what I should be doing, I started out just using jQuery to hide the non-selected missions but getting my head round Meteor and it seems a natural fit to use the data and reactivity to drive selection/local-filtering.
Is there any way the roundtrip can be avoided or am I just using it wrong?
By setting up a publish / subscribe relationship, you are creating a simplified form of database replication. Minimongo will have a copy of the data locally and execute the find() locally without a server roundtrip. If you are seeing network activity or calls to the server code, it is because meteor is regularly working behind the scenes to keep the subscription in sync with the server, not for your specific find.
This also means you have to wary of sending too much data to the client, so your server side publish function may want to filter by the specific fields needed by client, in addition to existing your selection criteria.

how to properly bind jquery ui behaviors in meteor?

I am trying to create a group of draggable DOM objects using jQuery UI's .draggable() that are populated through Meteor subscriptions. The code I came up with looks like
Meteor.subscribe('those_absent', function() {
$( "li.ui-draggable" ).draggable( { revert: "invalid" } );
});
Meteor.subscribe('those_present', function() {
$( "li.ui-draggable" ).draggable( { revert: "invalid" } );
});
These correspond with some Meteor.publish() calls, so that any time the collection changes, the .draggable() behaviour will be attached. At least, that was my intention.
However, it only works once - once one of these <li>'s has been dragged and dropped, then they are no longer draggable at all.
When the objects are dropped, I'm firing a custom event that is attached to the Template for the item like so
$( "#c_absent .inner-drop" ).droppable({
drop: function( event, ui ) {
ui.draggable.trigger('inout.leave');
}
});
Template.loftie_detail.events = {
'inout.leave': function (e) {
Lofties.update({_id:this._id}, {$set: {present: 'N' }});
}
};
So, my thinking is that this change to the collection on drop should propagate through the pub/sub process and re-run the .draggable() line above. But it doesn't seem to.
The complete code for this can be seen here https://github.com/sbeam/in-out/blob/master/client/inout.js and the app is live at http://inout.meteor.com/ (there are some other probably unrelated issues with items randomly losing values or disappearing from the UI altogether)
So if my understanding of how pub/sub works in Meteor is off, it would be good to know. Or is there a more efficient way to achieve this UI behavior binding that works without it?
The way I have implemented this in my apps is with the method shown by #lashleigh.
I have a template event that listens using code like this :
Template.myDraggableItem.events({
'mouseover .workItem' : function() {
$(this._id).draggable();
}
});
Then I listen for the dragstop like this.
$('body').on('dragstop', '.myDraggableItem', function (e) {
// Update the collection with the new position
};
You can see the app that's using this code at aduno.meteor.com

Resources