Search code examples
meteoriron-router

Pagination with meteor and iron router not re-rendering


I'm really struggling to understand what's going on here. I have a template

<template name="gallery">
  <div>
    <a href="/">1</a><a href="/gallery/2">2</a><a href="/gallery/3">3</a>
  </div>
  {{#each art}}
    {{name}} 
  {{/each}}
</template>

Then I have routes like this

Router.map(function() {
  this.route('/', {
    template: 'gallery',
    data: function() {
      var page = 1;
      return {
        page: page,
      };
    },
  });
  this.route('/gallery/:_page', {
    template: 'gallery',
    data: function() {
      var page = parseInt(this.params._page);
      return {
        page: page,
      };
    },
  });
});

Following this article I'm using template subscriptions like this

  var G_PAGE_SIZE = 10;

  Template.gallery.onCreated(function() {
    var instance = this;
    var route = Router.current();
    instance.loaded  = new ReactiveVar(0);

    instance.autorun(function() {
      var pageId = parseInt(route.params._page);
      var page = pageId - 1;
      var skip = page * G_PAGE_SIZE;

      var subscription = instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
      if (subscription.ready()) {
        instance.loaded.set(1);
      }
    });

    instance.art = function() {
      return Art.find({});
    };
  });

  Template.gallery.helpers({
    art: function() {
      return Template.instance().art();
    },
  });

It works but when I click one of the links changing the route the page doesn't re-render

So ... some other answer on here said that's because there's no reactive connection between data changing on the route through the router and the template.

So I tried use route.state which as a ReactiveDict (not sure where that came from). If it's part of iron:router or if it's part of reactive-var

Anyway I changed the code to

Router.map(function() {
  this.route('/', {
    template: 'gallery',
    data: function() {
      var page = 1;
      this.state.set('page', page);  // <------------ADDED
      return {
        page: page,
      };
    },
  });
  this.route('/gallery/:_page', {
    template: 'gallery',
    data: function() {
      var page = parseInt(this.params._page);
      this.state.set('page', page);  // <------------ADDED
      return {
        page: page,
      };
    },
  });
});

In the onCreated

  Template.gallery.onCreated(function() {
    var instance = this;
    var route = Router.current();
    instance.loaded  = new ReactiveVar(0);

    instance.autorun(function() {
      var pageId = route.state.get('page'); // <--------- CHANGED
      var page = pageId - 1;
      var skip = page * G_PAGE_SIZE;

      var subscription = instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
      if (subscription.ready()) {
        instance.loaded.set(1);
      }
    });

    instance.art = function() {
      return Art.find({});
    };
  });

  Template.gallery.helpers({
    art: function() {
      return Template.instance().art();
    },
  });

That doesn't work either except sometimes. In the interest of debugging I added a console.log to the route

Router.map(function() {
  this.route('/', {
    template: 'gallery',
    data: function() {
      var page = 1;
      this.state.set('page', page);
      console.log("/root");  // <------------ADDED
      return {
        page: page,
      };
    },
  });
  this.route('/gallery/:_page', {
    template: 'gallery',
    data: function() {
      var page = parseInt(this.params._page);
      this.state.set('page', page);
      console.log("/gallery/" + page);  // <------------ADDED
      return {
        page: page,
      };
    },
  });
});

Suddenly it starts working !???!@!? I remove the console.logs and it stops

I also tried adding actions without the console.logs

Router.map(function() {
  this.route('/', {
    template: 'gallery',
    data: function() {
      var page = 1;
      this.state.set('page', page);
      return {
        page: page,
      };
    },
    action: function() {              // <- added
      var page = 1;                   // <- added
      this.state.set('page', page);   // <- added
      this.render();                  // <- added
    },                                // <- added
  });
  this.route('/gallery/:_page', {
    template: 'gallery',
    data: function() {
      var page = parseInt(this.params._page);
      this.state.set('page', page);
      return {
        page: page,
      };
    },
    action: function() {                      // <- added
      var page = parseInt(this.params._page); // <- added
      this.state.set('page', page);           // <- added
      this.render();                          // <- added
    },                                        // <- added
  });
});

That doesn't work either.


update

So using Router.current().data().??? seems to make it work. It's now this

Router.map(function() {
  this.route('/', {
    template: 'gallery',
    data: function() {
      var page = 1;
          // <--- removed this.state() stuff
      return {
        page: page,
      };
    },
         // <---- removed action stuff
  });
  this.route('/gallery/:_page', {
    template: 'gallery',
    data: function() {
      var page = parseInt(this.params._page);
          // <--- removed this.state() stuff
      return {
        page: page,
      };
    },
         // <---- removed action stuff
  });
});

helper

  Template.gallery.onCreated(function() {
    var instance = this;
    instance.loaded  = new ReactiveVar(0);

    instance.autorun(function() {
      var route = Router.current();    // <----- Moved from 3 lines up
      var pageId = route.data().page;  // <--------- CHANGED
      var page = pageId - 1;
      var skip = page * G_PAGE_SIZE;

      var subscription = instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
      if (subscription.ready()) {
        instance.loaded.set(1);
      }
    });

    instance.art = function() {
      return Art.find({});
    };
  });

  Template.gallery.helpers({
    art: function() {
      return Template.instance().art();
    },
  });

No idea if this is now correct or if I'm just getting lucky like with the console.log editions


Solution

  • My guess is that your autorun function is not being re-run because you are not using any reactive variables. I'm not sure if Router.current() or Router.current().data() are reactive, but if they're not, then this explains the issue clearly for me. To test this, try putting some console logging in your autorun function.

    Now, I would proposed some revisions that may work for you.

    Routing code

    Redirect to '/' URL to '/gallery/1' so you can re-use routing code

    Router.route('/', function(){
        this.redirect('/gallery/1');
    });
    

    Sending the page number in the data object to your template is important so that we can use this object in our autorun function

    Router.route('/gallery/:_page', {
        template: 'gallery',
        data: function() {
            return {
                page: this.params._page,
            };
        }
    });
    

    Also, you don't need to use parseInt method unless you are trying to re-route or handle that exception in your routing code.

    Gallery onCreated and helpers

    There is a Template.subscriptionsReady helper provided by Meteor so you don't have to manually check if the subscription is loaded unless you really want it. Here's the Template.subscriptionsReady documentation

    Here's a simplified version of your onCreated method using the template.data object which was built by the routing code above. Using the autorun wrapper is important so that it will re-run when template.data is changed by your router.

    var G_PAGE_SIZE = 10;
    
    Template.gallery.onCreated(function() {
        var instance = this;
    
        instance.autorun(function() {
            // access the data object built by your route code
            var pageId = instance.data.page;  
    
            var page = pageId - 1;
            var skip = page * G_PAGE_SIZE;
    
            instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
        });
    
    });
    

    Define access to your reactive data sources using the template.helpers method.

    Template.gallery.helpers({
        art: function() {
            return Art.find({});
        }
    });
    

    Note: Not necessary to define your helper in the onCreated and template.helpers