[Worked Around Solved] getMeteorData() is slow

@sashko i’ve noticed that a lot of times getMeteorData would re-run more times than it needs to which can cause a lot of UI lag… one example was every key press triggered multiple re-renders which made it a bit laggy.

Is there a set of do’s/dont’s as far as how to structure the function? One thing I noticed is that scoping down the things tracker is watching can help (either with fields or wrapping with another component).

To be honest I haven’t used it lately (mostly Redux) so perhaps these were resolved already.

@sylar can you paste in your getMteorData method in so we can troubleshoot?

@sashko @SkinnyGeek1010

getMeteorData() {
    return {
      user: Meteor.user() // subscribed elsewhere
    };
  },

The idea is to tap into the user’s profile:

this.data.user.profile.*
...

componentDidMount(){
  let u = this.data.user.profile.name;
  console.log(u);
},

...

In Chrome you’ll see that the user object is there and while in Firefox, it’s undefined.

A component can mount long before the collection used in getMeteorData is ready. That’s why sometimes you will get undefined.

4 Likes

@sylar Could you try this? Not sure exactly if it would blow up but if you can nerf it until it’s ready that may work:

getMeteorData() {
    return {
      userProfile: Meteor.user().profile || {}
    };
},

still, people just ignore the edge case when user is not logged in or user collection is not yet on client.
So render something other when it is not defined, do not try to render that user in render() till it is defined.
Render some placeholder element instead.

1 Like

In general, in Meteor, we’ve gotta be careful info we want to be reactive. If we use Meteor.user() in a computation, then that will be reactive when absolutely any info inside the user object changes, which can be lots if the user object has lots of changing info.

I read somewhere (can’t find it at the moment) that if you want to react to a specific property of a document, you can do just that with the fields option, which will cause reactive updates less often and perform better:

Meteor.users.findOne(id, {fields: {"profile.foo": 1}}) // react only to changes on profile.foo

Additionally, we can throttle reactivity and limit it to, say, at most one update per 2-second time window:

let profileFoo = new ReactiveVar('') // initial value of empty string

let setProfileFoo = _.throttle(value => profileFoo.set(value), 2000)
                                     // ^ fire this logic at most once in any
                                     // given 2-second period of time.
                                     // See http://underscorejs.org/#throttle

Tracker.autorun(computation => {
    let foo = Meteor.users.findOne(id, {fields: {"profile.foo": 1}})
    setProfileFoo(foo)
})

class Foo extends React.Component {
    render() {
        // this render method will only fire at most once per 2 seconds.
    }
    getMeteorData() {

        // will only change at most once in any 2-second time window, and only on the profile.foo property!
        let foo = profileFoo.get()

        return {foo}
    }
}

This getMeteorData is “fast” now. :wink:

11 Likes

I’ve used fields before but I didn’t think about throttle… nice! :thumbsup:

5 Likes

Nope: Can we solve React cursor inefficiency? (vs Blaze)

Isn’t that the reason why TrackerReact does this:
https://github.com/ultimatejs/tracker-react/blob/master/tracker-react-mixin.js#L14

We use TrackerReact instead of MeteorGetData and, in case anybody tried, would be interested to know if there is any difference. We actually use it in production and render about 1000 items in virtual lists :?

4 Likes

Sorry for the absence. UK bed time called but Im awake now. I’ll go through the replies and see what works best. Thanks for all replies!

Edit:

I think I have not sent the message correctly. Why your solution will not work? I’m not rendering anything so I can not render something else while I wait on their data to be loaded in client. Maybe what I want can be done another way? I’m still learning Redux but for now I’ll try with pure React and Meteor.

I want to bind a user profile field. So far what I did only works in Chrome but getMeteorData() is slow in other browsers. The scenario is, when a user is on their profile page, all fields will be blank. Now they will fill in the blanks then hit update. Now…those fields have the value of what was entered and they can update at will.

So first they are presented with a blank field:

When already filled out and updated, they can still see what was inputted:

Should be able to make changes:

My work around this is this:

...

componentDidMount() {
  let p = this.data.user.profile;
  console.log(this.data.user); // load instant in chrome. Undefined in FireFox :frowning:
  if ( isMounted() ) {
    this.setState({fname: p.fname});
  } 
},

...

I really need the state so the fields return the values of what was saved instead of having it blank.

Now that field would look like:

<input type="text" value={this.state.fname} />

This will not work because the value is permanently set and cant be changed/updated:

<input type="text" value={this.data.user.profile.fname} />

even with fast-render, do not expect user collection to be available all the time.
So test if this.data.user is available inside render(), if not return placeholder dom element.
If it is available you can return your form.
Nothing is needed in componentDidMount(), that getMeteorData will take care of re-renders during all changes of user record.

What I need is, when this.data.user is ready then the state can be updated. Seems Meteor is not for me I’m wondering.

this already handle it
this.user.data is already state and every time something change there, it get re-rendered

Ok but how to have something like this, I know this wont work:

getInitialState() {
 return {
  fname: this.data.user.profile.fname
 }
}

I must have this.state.* because I want them to be able to change any values in a field.

TrackerReact looks amazing dude! What’s the catch? Is there a negative to using this instead of Meteor’s mixin?

Tried that too. Same issue fetching user data.

when you have your subscription in some higher component, than use that component to render form only when it is already ready and pass there props whatever you want and do not render that form component before user data are ready.

that way you can initiate your form state based on passed properties from parent component.

It’s automatically subscribed as it’s set to null:

Server:


Meteor.publish(null, function () {
  if (this.userId) {
    return Meteor.users.find({_id: this.userId},
                             {fields: {'regtype': 1, 'profile': 1}});
  } else {
    this.ready();
  }
});

that does not mean it is available during client startup (especially if you dont use fast-render)

Don’t copy this.data into state. Instead, use this.data as the default, and override with this.state like so:

myValue = this.state.myValue || this.data.myValue;

This way, the value will update, unless the user has overridden it.

1 Like