Meteor Tracker not receiving subscriptions automatically - javascript

I'm creating some templates using Template.onCreated, and am then using Tracker.autorun to make some subscriptions and then collect data from the Server and store them on the client's MiniMongo.
Then, I can leave said Template and eventually route myself back. However, I'm noticing that upon coming back, Blaze is not capturing my changes. When routing back, it seems that onRendered is called instead of onCreated. This makes sense, however, onRendered isn't quite reacting to all my data changes yet. I've noticed it's because minimongo doesn't actually have data in it for onRendered.
However, I've noticed it can get the object by refreshing the page (thus calling onCreated and subscribing again and getting a fresh set of data from the server) My workaround is to re-subscribe in onRendered, but I feel like i shouldn't have to do this because I already initially subscribed in OnCreated. Is this an issue with Meteor, is DDP just going slow?
Let's say I have some default generic collection called Objects.
const Objects = new Mongo.Collection("Objects");
So in my onCreated I subscribe to a collection as follows
Template.foo.onCreated(function () {
var self = this;
self.autorun(function () {
const objId= FlowRouter.getParam("id");
self.objectSubscription = Meteor.subscribe('obj', objId, {
onReady: function () {
},
onStop: function (error) {
if (error) {
alert('error')
}
}
});
Then in onRendered I have to hack it and unnecessarily re-subscribe because I don't have objId i.e. I can't just use Objects.find(),
Template.foo.onRendered(function () {
const objId= FlowRouter.getParam("id");
Meteor.subscribe('obj', objId, {
onReady: function () {
$('#obj').addClass('highlight');
},
onStop: function (error) {
if (error) {
alert('error');
}
}
});
//I feel like I should just be able to do
//const obj = Objects.findOne({_id:objId})
//$('#obj').addClass('highlight')
});
Any insights, why would I be losing data in minimongo? And is it bad practice to "re-subscribe"?

onCreated and onRendered aren't reactive, but template helpers are. So, to constantly detect changes in those two you'd have to create a reactive context and place it inside them which can be achieved using Tracker.autorun. Also, be advised that it's better to keep onRenderd for DOM manipulations and onCreated for subscriptions and creation of reactive variables.
You don't need double subscriptions to detect changes and IMO this is generally a bad practice.
Template.foo.onCreated(function onCreated() {
const self = this;
this.autorun(function() {
const id = FlowRouter.getParam('_id');
self.subscribe('obj', id);
});
});
Template.foo.onRendered(function onRendered() {
const self = this;
// this will run after subscribe completes sending records to client
if (self.subscriptionsReady()) {
// do whatever you want
$('#obj').addClass('highlight');
}
});

Related

Meteor - Display the Email Address of the Logged In User

I am trying to display the email address of the logged in user with Meteor.
I am using the command Meteor.user().emails[0].address -- and this works sometimes only. Other times it is undefined. This is because sometimes the page renders before the User's collections is available.
However, I am using React and not blaze. Every solution online suggests using Meteor.subscribe() in the onCreated part of the template. But I cannot figure out React's equivalent and I cannot figure out how to wait for the User collection before rendering.
Updated to use Meteor.autorun which accepts a callback function that runs whenever Meteor's reactive sources update.
Meteor.subscribe accepts an onReady optional callback. I would attach to the componentWillMount lifecycle event on your React component, setup your meteor subscription, and cause a state change once onReady has fired. Here is some rough example code;
var Foo = React.createClass({
componentWillMount: function() {
var _this = this;
// Setup meteor subscription
Meteor.autorun(function () {
_this.setState({
user: Meteor.user(),
});
})
},
render: function() {
// Render nothing until we have a user
if (!this.state || !this.state.user) {
return null;
}
// Render the address when we have the user
return (
<div>{this.state.user.emails[0].address}</div>
);
}
});
Relevant docs: http://docs.meteor.com/api/pubsub.html#Meteor-subscribe

React Flux implement dispatch chain

i'm trying to use React with Flux architecture and stumbled on one restriction which i can't handle.
Problem is as following:
There's a store which listens to an event. Event has object id. We need to fetch object if needed and make it selected.
If store doesn't have object with this id - it's queried. In callback we dispatch another event to store which is responsible for selection.
If store has object - i'd like to dispatch selection event, but i can't because dispatch is in progress.
Best solution i came up with so far is wrapping inner dispatch in setTimeout(f, 0), but it looks scary.
Actually the problem is quite general - how should i organize dispatch chain without dispatch nesting (without violating current Flux restrictions) if each new dispatch is based on previous dispatch handling result.
Does anybody have any good approaches to solve such problems?
var selectItem(item) {
AppDispatcher.dispatch({
actionType: AppConstants.ITEM_SELECT,
item: item
});
}
// Item must be requested and selected.
// If it's in store - select it.
// Otherwise fetch and then select it.
SomeStore.dispatchToken = AppDispatcher.register((action) => {
switch(action.actionType) {
case AppConstants.ITEM_REQUESTED:
var item = SomeStore.getItem(action.itemId);
if (item) {
// Won't work because can't dispatch in the middle of dispatch
selectItem(item);
} else {
// Will work
$.getJSON(`some/${action.itemId}`, (item) => selectItem(item));
}
}
};
Are you writing your own dispatcher? setTimeout(f, 0) is a fine trick. I do the same thing in my minimal flux here. Nothing scary there. Javascript's concurrency model is pretty simple.
More robust flux dispatcher implementations should handle that for you.
If ITEM_SELECT is an event that another Store is going to handle:
You are looking for dispatcher.waitFor(array<string> ids): void, which lets you use the SomeStore.dispatchToken that register() returns to enforce the order in which Stores handle an event.
The store, say we call it OtherStore, that would handle the ITEM_SELECT event, should instead handle ITEM_REQUEST event, but call dispatcher.waitFor( [ SomeStore.dispatchToken ] ) first, and then get whatever result is interesting from SomeStore via a public method, like SomeStore.getItem().
But from your example, it seems like SomeStore doesn't do anything to its internal state with ITEM_REQUEST, so you just need to move the following lines into OtherStore with a few minor changes:
// OtherStore.js
case AppConstants.ITEM_REQUESTED:
dispatcher.waitFor( [ SomeStore.dispatchToken ] );// and don't even do this if SomeStore isn't doing anything with ITEM_REQUEST
var item = SomeStore.getItem(action.itemId);
if (item) {
// Don't dispatch an event, let other stores handle this event, if necessary
OtherStore.doSomethingWith(item);
} else {
// Will work
$.getJSON(`some/${action.itemId}`, (item) => OtherStore.doSomethingWith(item));
}
And again, if another store needs to handle the result of OtherStore.doSomethingWith(item), they can also handle ITEM_REQUESTED, but call dispatcher.waitFor( [ OtherStore.dispatchToken ] ) before proceeding.
So, in looking at your code, are you setting a "selected" property on the item so it will be checked/selected in your UI/Component? If so, just make that part of the function you are already in.
if(item) {
item.selected = true;
//we're done now, no need to create another Action at this point,
//we have changed the state of our data, now alert the components
//via emitChange()
emitChange();
}
If you're wanting to track the currently selected item in the Store, just have an ID or and object as a private var up there, and set it similarly.
var Store = (function(){
var _currentItem = {};
var _currentItemID = 1;
function selectItem(item) {
_currentItem = item;
_currentItemID = item.id;
emitChange();
}
(function() {
Dispatcher.register(function(action){
case AppConstants.ITEM_REQUESTED:
var item = SomeStore.getItem(action.itemId);
if (item) {
selectItem(item);
} else {
$.getJSON(`some/${action.itemId}`, (item) =>
selectItem(item);
}
});
})();
return {
getCurrentlySelectedItem: function() {
return _currentItem;
},
getCurrentlySelectedItemID: function() {
return _currentItemID;
}
}
})();
Ultimately, you don't have to create Actions for everything. Whatever the item is that you're operating on, it should be some domain entity, and it is your Store's job to manage the state of that specific entity. Having other internal functions is often a necessity, so just make selectItem(item) an internal function of your Store so you don't have to create a new Action to access it or use it.
Now, if you have cross-store concerns, and another Store cares about some specific change to some data in your initial Store, this is where the waitFor(ids) function will come in. It effectively blocks execution until the first Store is updated, then the other can continue executing, assured that the other Store's data is in a valid state.
I hope this makes sense and solves your problem, if not, let me know, and hopefully I can zero in better.

Listen to Reflux actions outside of stores

I'm trying to figure out how to fit ajax calls into the flux/reflux way of doing things. From the scraps of info I've found online it seems correct to separate the API logic from the stores.
Starting there, I've created a client API that makes requests to the server. Now I want these functions them listen to my actions, then make a request, and upon success to trigger other actions that subsequently update the stores with the new data from the server.
How can go about having something similar to the reflux createStore method for my API? Or, is there some other clean way for my server calls to listen to actions?
The store is the glue between your actions and the API. Just like answered in https://stackoverflow.com/a/27487312/274483
The difference in your case is that you don't perform the ajax request directly in the store, but go through your API-class, populate the store with the data returned from your API and trigger an update of the store.
My Api is just a plain old javascript object (POJO) like this. It's in ES6 but you should get the idea:
import request from 'superagent';
// Other helpers functions and setup
let handle = (err) => {
// error handling stuff
};
export default {
create(user, cb) {
return request
.post(server + '/api/users/new')
.send(user)
.on('error', (err) => {
handle(err);
cb(err);
})
.end(cb);
},
login(user, cb) {
// Post some more stuff
}
};
Then, I call it in my Store like so:
import Reflux from 'reflux';
import UserActions from '../actions/UserActions';
import Api from '../api/UserApi';
const UserStore = Reflux.createStore({
listenables: [UserActions],
getInitialState() {
// insert stuff
},
onCreate(user) {
Api.create(user, (err, res) => {
if (err) {
console.log(err);
} else {
// Do something with res
// this is for JSON, your API might be different
let user = JSON.parse(res.text);
this.update(user);
}
})
},
onLogin(user) {
// login stuff
},
// More methods
update(user) {
this.currentUser = user;
this.trigger(user);
}
});
I don't update my store and this.trigger() until the api call returns.
A smarter idea might be to optimistically update:
// inside the todo store
onCreate(todo) {
Api.create(todo);
this.update([todos].concat(this.todos));
},
update(todos) {
this.todos = todos;
this.trigger(todos);
}
Obviously, this is one way to do it, but it's certainly not the only way.
But, the main idea is the Store uses the API.
The API is not part of the data flow of:
Action->Store->Component->Action etc.

Clean up requests and callbacks when switching routes

I have a backbone app that loads some data from an API into a chart. This app has several tabs that can be navigated through to access these different charts, and every tab executes a route. Every chart is an instance of ChartView with the appropriate data put in.
I have a problem that is caused by some API calls that may take a while. When the requests takes too long, some users start to cycle quickly through the tabs, executing each route quickly after each other. This fires up all the collection fetches which eventually messes up the interface because some callbacks will do a bit of rendering.
So my question is, how can i make sure that every time a new route is loaded (even if it is done in quick succession) that all pending or started requests are stopped so no "request-success" callbacks are fired?
I would suggest, override Backbone.Views remove method. With regular stopListening, abort ajax calls, also set a flag like this.removed=true. In your render function check for removed flag, if present don't render. If click has been done very quickly, you may need to check it before making any calls.
Based on Ravi Hamsa's reply I implemented an object that is injected into each route that keeps the requests and whether or not the route is still relevant.
It looks like this:
var RouteContext = function RouteContext() {
this._xhrs = {};
this.stopped = false;
this.manageRequest = function(xhr) {
this.xhrs.push(xhr);
}
this.stop = function() {
this.stopped = true;
_.invoke(this.xhrs, 'abort');
}
}
I override the Backbone.Router route method like this:
route: function(route, name, callbackFactory) {
var callback;
if (_.isFunction(callbackFactory)) {
var context = new RouteContext();
callback = callbackFactory(context);
// When a new route is opened, this route should be stopped and all
// corresponding jqXHR's should be aborted.
App.mediator.on('tabClicked', function() {
context.stop();
});
} else {
callback = callbackFactory;
}
return Backbone.Router.prototype.route.call(this, route, name, callback);
}
I can now create a new route method with this context like this:
var routeFactory = function(routeContext) {
// Might do some route initialisation here.
return function() {
this.reset(routeContext);
// This function is the actual function that will be called when a route is triggered.
if (routeContext.stopped === false) {
myView.renderChart();
}
}
};
// Register the route on the router.
myRouter.route('route', 'name', routeFactory);
Because a route can be called multiple times I reset the RouteContext back to it's original state when the route is called again.
And in my route I keep checking everywhere I need to do rendering whether the routeContext.stopped is still false. If it is true I don't do the rendering.

Observe every rendering (even conditionals) in EmberJS

I want to get a function running after any rendering in EmberJS has happened (globally), especially after the "switch" in an {{#if}}-{{else}}-block.
Subscribing to the render event with
Ember.subscribe("render", {
before: function (name, timestamp, payload) {
},
after: function (name, timestamp, payload) {
// function call
}
});
works quite well but is not called after the rerendering via the if-else-switch.
So what I want to do is listen to the rendering globally and be noticed at the end of it.
Best regards
Martin

Categories

Resources