Bug: onRendered executes too early

Your last code snippet is the proper way to do it. If you ever have an {{#each}} block that’s waiting for a subscription, then the DOM will be ready before your data is loaded in that block (so onRendered works as intended).

The usual pattern to avoid the Tracker.afterFlush “workaround” is to refactor whatever is inside your {{#each}} block into a separate template and use that template’s onRendered function instead.

9 Likes

Since “Rendered” is past tense, we should expect that the page has been completely loaded (all DOM elements have been rendered) once code in onRendered begins execution. My experience above shows that a page is not completely loaded when onRendered starts to execute. Therefore either this is a bug and should be fixed or “onRendered” should be renamed to “onRendering” or “onAlmostRendered” in order to avoid confusion.

All that code with autorun and this.subscriptionsReady() makes sense to have in onCreated but not in onRendered.

2 Likes

Data source in Meteor is reactive, i.e. async. So if you full page rendering depends on data, it could and usually happens after onRendered. Usual practice is to check subscriptonsReady in template instead of in code to avoid the confusion:

<template name="abc">
    {{#if Template.subscriptionsReady}}
     ... do what you need
    {{else}}
     ... show loading message ..
    {{/if}}
</template>

See here: https://www.discovermeteor.com/blog/template-level-subscriptions/

I understand your frustration. I think everyone goes through a little bit of this when they start with Meteor. This is a side-effect of reactive data. You have to stop thinking like pages are rendered server-side and sent to the client. Your template will not have initial data when it is rendered. Therefore, any DOM elements that are scoped under an {{#each}} block (which depends on data) will not exist. It is not a bug, I assure you.

In the old days, we used iron:router's waitOn feature to wait for initial data before rendering the page (which turns out isn’t great for performance at all), and onRendered would work as you are describing.

2 Likes

I think the terminology here is OK because a template can be rendered with data or without data and still be considered rendered.

Having said that I do think there should be something closer to first class support for determining when the current data has been fully rendered into the DOM. Combining subscriptionsReady with afterFlush is presumably the most correct way to do this but that is a lot of boilerplate for what might be the most commonly desired hook point.

1 Like

Technically, to be 100% sure that you call your callback at the right time, you typically want your callback to be

Subscription Ready -> After Flush -> Request Animation Frame -> callback.

This is because while offset() will force that element to calculate it’s layout, it doesn’t force the entire dom to finish pending layout calculations which may or may not be relevant to your scroll position. Request Animation Frame will cause a callback to fire after layout calculations are done but before paint is fired, giving you a chance to do your scroll then.

But why should that require a bunch of “boilerplate?” One helper function automagically handles this for all use cases:

function whenLayoutReady(subs, callback) {
    //ducktyping to force to an array
    if (!subs.length) {
        subs = [subs];
    }
    //autorun to detect when all subs are ready
    Tracker.autorun((c) => {
        if (_.all(subs, sub => sub.ready()) {
            c.stop();
            Tracker.afterFlush(() => {
                window.requestAnimationFrame(callback);
            });
        }
    });
}
4 Likes

Any help on this? Does onRendered executes only after the template is fully loaded? We have lots of dropdown in a page that uses materialize and lots of data too to be loaded from collections, the materialize feature disappeared without any error.

Hi, I’m wondering on where to put this codes in some js files I have on my meteor app, can you guide me please?

Thanks!

I didn’t go through the whole post, but we use _.defer or a timeout inside onRendered to give DOM engine the time to properly create all elements.

2 Likes

jQuery is very often a pain in the ass when using Blaze and reactivity. Because Blaze takes the next render event to update the UI, you need to take the one after, or the element that you want to hook into with jQuery isn’t available yet.

My solution, just hook any jquery init into the next render event. Never fails.

Meteor.setTimeout ->
  # Your jquery stuff in here
, 0

I have the same problem too.
Could you full example @ramez, @satya ?

This is actually due to the DOM sending ready event when the elements have not fully renderd. We also successfully used _.render – not just timeout

Please detail

Template.myTemplate.onRendered(function () {
   // How to use _.render???    
});
Template.myTemplate.onRendered(function () {
   var self = this;
   _.defer(function() {
      $("#" + self.data.id).val(self.data.value);
   });
});

I tried _.defer but still not working. We have lots of materialize dropdown in the page (because the form fields are extandable). :frowning:

Try adding a timeout with an extreme delay (say 5000) to see if that is the problem. I don’t know materialize but it’s possible that there is delay in its own rendering of the DOM. In which case, you are not waiting for the template’s onRendered, you are waiting for another library to process the DOM for you.

We use Semantic-UI and it has almost always processed in reasonable time.

This is really a bad idea.

Instead, an autorun should exist which monitors for the data subscriptions to be ready.

Template.myTemplate.onRendered(function () {
   var self = this;
   this.autorun((c) => {
     if (this.criticalSub.ready()) {
       //do key thing now with dom
       c.stop();
     }
   });
});

We usually subscribe in onCreated, onRendered relates to actual DOM rendering.

absolutely correct, however that subscription should be stored on the template instance so that it can be referenced there.

You’ll notice that I did NOT subscribe there, I only checked if the important sub was ready. In short, onRendered is responsible for handling “did it render” but has no care about how to actually subscribe.

Hellstad is correct.

For example, we use Materialize in our project. Materialize uses many different functions to update via JS so you have to use it in onRendered functions.

As an easy to use solution, what I did was made a template called “materializeLoader”. This contains all the js required to update the page correctly.

Then, on any page using the elements which require JS, I can simply throw {{materializeLoader}} in to it, and bam, good to go! The loader will run AFTER everything else on your page.

In situations where your page is iterating through data, such as in #each, simply throw the loader inside the #each template, and you will have it updated each time the data is updated.