[Worked Around Solved] getMeteorData() is slow

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

Fast-Render makes the userId immediately available. You should use that for auth checks, separate from the user collection. And if you’d want, you could check the user collection and block re-rendering in a componentShouldUpdate until it becomes available, i.e. combined with SSR.

@sylar maybe you want to check out TrackerReact. Here is a basic example, my only one public, that shows how you could deal with user and user authentication:

@sergiotapia we have not found any drawbacks. And since the package is literally just a few lines, we decided to bring it into production. On the repo was a issue discussion about that.

One of the benefits is that it brings meteors known plug & play, without freezing states, to react in meteor.

2 Likes

Ok ill try this and see. Thanks. But Im new to react and more studies needed.

1 Like

@sashko Thank you! This worked perfectly!

1 Like

@sashko Your method did worked; with more code but a user could not clear the field. This made me invested more about Meteor and I’ve found Meteor.autorun.

I had to change my original code from:

To:

Meteor.publish("userData", function () {
...

I needed to add the subscription to FlowRouter:

FlowRouter.route('/profile', {
  name: 'profile',
  action(){
    var subs = Meteor.subscribe('userData');
    Meteor.autorun(function() {
      if (subs.ready()) {
        ReactLayout.render(App, {
          nav: <Nav />,
          content: <Profile user={ Meteor.user() }/> // passing the props to getInitialState()
        });
      }
    });
  }
});

Now my getInitialState() looks like this:

...

fname: this.props.user.profile.firstName,

...
formSubmit() {
  let fname = this.state.fname;
  let students = {
   firstName: fname,
   ...
  };
  Meteor.users.update( { _id: Meteor.userId() }, {
    
    $set: {profile: students}
  });
},

My render():

<input type="text value={this.state.fname} onClick={this.formSubmit} placeholder="First name" />

Bam! Two-way binding at its best with Meteor and React! Works fast across FF, Safari and Chrome.

Credit to this goes to @Steve

1 Like

Meteor.user() || {profile:{}}