Meteor 1.5 publication/method pagination

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

1 Like

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.

1 Like

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?

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!

1 Like

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.

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.

Hi @methodx I’m using React

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.

1 Like

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;

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
2 Likes

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;
  }
});
1 Like

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.

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

3 Likes

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

3 Likes

Thanks for sharing @razor7 you beat me to it :slight_smile: I’ll take a look as I’m also in need for something re-usable.

Cool, maybe you can take a look at the reordering issue

Yup, but how does it compare to the real-time pub solution now that you’ve experienced both?

I think it’s ok if you don’t need reactivity. Load time should be the same.

Hi! The developer of kurounin:pagination just updated the module to enable non reactive scenarios. https://github.com/Kurounin/Pagination and I have updated my example https://github.com/mgscreativa/kurounin-pagination-react-example

Regards!

2 Likes

Thank you for the example app. I’m having a look at it, as I need a React paginated table. Would it be better to have columns be a prop that’s passed into DataTablesList instead of being defined withinDataTablesList, so that DataTablesList can be reused for other sets of data?