Meteor 1.5 publication/method pagination


#1

Hi! I’m a complete Meteor newcomer. I need to know a simple way to display a list of users paged with a limit of 10 per page. I have the table layout but I’m finding very confusing the pagination approach for Meteor.

I may use a publication for realtime, but I’m good if this can be achieved with a method.

Thanks!


Fetching subscribed data through method
#2

It’s a bit squirrelly the first time you do something like this but the concept is simple.

You subscribe to a publication limiting what is returned to 10.

When the user wants to see more by clicking a button etc you bump the limit up to say 20. The variable you use to manage the limit must be reactive (I use ReactiveVar for this typically but u can also use session) and is passed into your publication via your subscription. End result is meteor will then return 20 records dynamically and your view will be updated automatically.

Check out https://guide.meteor.com/data-loading.html - see the bit about reactively changing subscription arguments.

There might also be a package someone can suggest to simplify this but IMO once u get the hang of it it’s pretty straight forward.


#3

Hi! @drollins thanks for your reply.

I’m considering seriously to drop publications and just stick with methods for this thing. I mean, I just need a tabulated list of users with some fields for an admin to check it out and eventually edit that user or delete it, and that’s all.

Maybe for this the methods approach is better that the pub/sub approach.

What do you think?


#4

Not really.
And with pagination you would automatically get the updated fields after the edit.

Anyway, what do you find overly complicated in the approach shown in https://guide.meteor.com/data-loading.html#pagination?


#5

I need the "page-by-page” style, not the proposed “infinite-scroll”


#6

Are you using blaze? If so, maybe I’m missing something, but how are you getting data to your templates without sub pub? It seems like you are saying you’re using meteor methods to pull data into your blaze templates? If so do you mind displaying a snippet of code the demonstrates what you’re doing?


#7

I’d say go with a method. Real time for everything is way overrated. What front end library are you using? React, Blaze, Vue?


#8

I’ll also go for methods for tables with pagination specifically, realtime for tables make things complicated in my opinion, you don’t want items to popup randomly when the user is looking at certain page. One option is just to update the DB and indicate to the user there are changes and ask them to refresh.

Real-time is great in many other scenarios but for data tables with pagination it’s better to give users a bit more control and ask them to refresh in my opinion.


#9

I’m real time, sub/pub with everything, including paginated tables. How do you go “non” realtime? Are you guys pulling the data back in a Meteor method callback?


#10

Yeah, you can just export method and call it on the client (kind like rest call) without sub/pub. However Meteor offers the additional capability over rest in that you can simulate those methods optimistically on the client, I usually start with server and only optimize (client simulate) when needed.

Here my is typical template for server method

export const aadams = new ValidatedMethod({
  name: 'aadams',
  validate: new SimpleSchema({}).validator(),
  run({}) {
    let result;
    if (Meteor.isServer) {
      log.call('aadams called');
      result = false;
      log.result('aadams returns ');
    }
    return result;
  },
})

note that any code outside the if (Meteor.isServer) {} block will be simulated at the client.

And from the client after importing the method you can do:

// call server method aadams
aadams.call({}, (err, res) => {
  let result;
  if (err) {
    console.log(err);
  } else {
    result = res;
    console.log(res);
  }
  return result;
});

I would only use pub/sub when the use case truly requires real-time feedback (newsfeed or gameplay for example) and use methods elsewhere, this is to avoid an unnecessary pub/sub bottlenecks and to make it easier to migrate to Apollo (or less likely rest) should I need to. You can find more details in the Meteor Guide. I hope that helps!


#11

Thanks, it does help. On the callback, you guys are not populating mini-mongo like we do with sub/pub right? If so you guys are using custom objects in the callbacks?

I make a few Meteor method calls (not using a package, just raw Meteor methods) to retreive data from the server, in the callback I assign to a custom variable is all – mostly one-offs.


#12

For me, no mini mongo. I’m pretty sure mini mongo is only used to track changes for the purpose of real time updates, so it’s use with a method would be redundant.


#13

Hi @methodx I’m using React


#14

Yup, just JS objects back to the client for methods, and mini-mongo for pub/sub.

The other thing you need to be mindful of when using methods is exception handling. Take a look at this server code:

 if (Meteor.isServer) {
      log.call(`updatePlan called for customer ${customerId} to plan {${planId}}`);
      const customer = Customers.findOne(customerId);
      if (!customer) {
        throw RemoteErr('customers.updatePlan.noCustomerFound', 'can\`t find customer with given id'); 

....

Here I’ve custom called called RemoteErr, which is basically a wrapper over Meteor Error

export const RemoteErr = (type, msg = 'something went wrong', e = '') => {
  log.err(msg, e);
  throw new Meteor.Error(type, msg);
}

And on the client you just handle the if (err) {} block in the callback.


#15

Thanks @alawi. When you need to return something to iterate over, like several records in a database, I use to use mini-mogo’s cursor and assign it to a helper that is consumed in Blaze with a #each statement. It’s basically an array of objects. I guess you build that server side an pass that down to the client callback.

it would look something like this?

// built with a foreach on the server to be passed to the client:

let CustomObject = [];
CollectionName.find({userId: '123'}).forEach(function(rec) {
  ...
});

// [ { first: 'allen', last: 'adams' }, {first: 'amir', last: 'rama' }, { first: 'alawi:, last: 'king' } ] 
return CustomObject;

#16

Perfect. That’s what I have experience with.

Pub/Sub Example

import React, { Component } from 'react'
import { StuffCollection } from '../../../api/collections'
import { Session } from 'meteor/session'
import { createContainer } from 'meteor/react-meteor-data'

class MyExampleComponent extends Component {
  constructor () {
    super()
    Session.setDefault('limit', 10)
    this.handleLoadMore = this.handleLoadMore.bind(this)
  }

  componentWillUnmount () {
    delete Session.keys['limit']
  }

  handleLoadMore () {
    const limit = Session.get('limit')
    Session.set('limit', limit + 10)
  }

  render () {
    const { isLoading, stuff } = this.props

    if (isLoading) {
      return (
        // Loading spinner
      )
    }
    ...
  }
}


export default createContainer(() => {
  let limit = Session.get('limit')
  const subscriptions = {
    stuff: Meteor.subscribe('stuff', limit)
  }

  return {
    stuff: StuffCollection.find().fetch(),
    isLoading: limit <= 10 ? !subscriptions.stuff.ready() : false
  }
}, MyExampleComponent)

Method Example

import React, { Component } from 'react'
import { Meteor } from 'meteor/meteor'

class MyExampleComponent extends Component {
  constructor () {
    super()
    this.state = {
      stuff: [],
      isLoading: true,
      limit: 10
    }
    this.handleLoadMore = this.handleLoadMore.bind(this)
  }

  componentWillMount () {
    this.getStuff()
  }

  getStuff () {
    Meteor.call('getStuff', limit, (err, res) => {
      if (err) {
        console.error('error', err)
      }
      if (res) {
        this.setState({
          stuff: res,
          isLoading: false
        })
      }
    })
  }

  handleLoadMore () {
    this.setState({
      limit: this.state.limit + 10,
      isLoading: true
    })
    this.getStuff()
  }

  render () {
    const { isLoading, stuff } = this.state

    if (isLoading) {
      return (
        // Loading spinner
      )
    }
    ...
  }
}

export default MyExampleComponent

Best practice for creating tables with pagination using React and WithTracker
#17

For returning arrays, I just extract the array from the collection using fetch() and return it to the client.

For example:

export const getComments = new ValidatedMethod({
  name: 'getComments',
  validate: new SimpleSchema({
    postId: {
      type: String
    }
  }).validator(),
  run({ postId }) {
    let result;
    if (Meteor.isServer) {
      log.call(`getComments called for post with id: ${postId}`);
      result = Comments.find({ postId }).fetch();
      console.log(result);
      log.result('getComments returning an array of comments');
    }
    return result;
  }
});

#18

Well, finally I’m using a mix of kurounin:pagination (https://github.com/Kurounin/Pagination), react-bootstrap-pagination (https://github.com/Kurounin/react-bootstrap-pagination) and react-data-grid (https://github.com/adazzle/react-data-grid)

This way I solved the pub/sub users list, but after all this discussion, I’m seriously considering to render my admin user list with methods.


#19

Decided to publish my approach using pub/sub system https://github.com/mgscreativa/kurounin-pagination-react-example hope it helps someone.


#20

Hi! @alawi @methodx @aadams

Just published a WIP React Data Grid implementation using Meteor methods. It’s a WIP because currently some parts of the implementation are not finished, or not working.

What works so far:

  • Fetch data from db using method and display it using react-data-grid
  • Use React lifecycle methods to manage non reactive data lifecycle, initial load and GUI state changes
  • Pagination limit changes
  • Search items using MongoDB $or by two fields (collection fields title and body)

What doesn’t work:

  • Column order: I think I implemented it fine, but somehow it doesn’t work as I expected. The reorder happens but React Data Grid column doesn’ t get the column arrow and in column order change it keeps the same ordering until I click the other column of reordering, very strange.

What’s not implemented yet:

  • Paginator: Sorry, maybe next week will try to implement a paginator. If you feel inspired, please try implement pagination maybe with some react bootsrap component

Here’s the repo https://github.com/mgscreativa/meteor-react-data-grid-with-methods