Alternative to ReactMeteorData mixin

I’ve done some work on an alternative to the ReactMeteorData mixin and I’ve come up with a working prototype:

https://github.com/huttonr/react-meteor-computations

It is still very early in development but I would love to know what you think. I know some work is being done on a replacement for ReactMeteorData and hope this may help to at least give another look on how this can be accomplished.

ReactMeteorComputations mixin

Introduction

ReactMeteorComputations is Meteor package supplying a custom React mixin ReactMeteorComputations which enables Meteor reactivity (Tracker) in React.

It is designed as an alternative to ReactMeteorData which has the following pitfalls:

  • The ReactMeteorData mixin forces all updates to reactive sources to update/rerender the component, bypassing shouldComponentUpdate and making it impossible to increase update efficiency without creating individual wrapper components.
  • All reactive dependencies are put into a single computation which is rerun in its entirety any time any of the dependencies change, meaning if you have five fetches from five different Mongo collections, they will all re-fetch if a single one of them changes.
  • Any changes made to props or state causing a component update also force and update of all reactive dependencies with no way to individually specify which reactive sources to update based on the props/state change.
  • Data from the reactive sources are not placed into this.state but instead are placed into this.data which, although is a proposed and possible future React design, does not feel the same as having the data in state (with the rest of your state) where a React component would ordinarily store it.

ReactMeteorComputations seeks to address these issues in following manner:

  • No forced updates. shouldComponentUpdate works fully and a change in a reactive dependency does not have to trigger an update/rerender of the component.
  • Reactive dependencies are put into seperate computations so they don’t all rerun when one changes.
  • Though seperate computations are used there is still support to return multiple datapoints from a single computation if you feel it would be more efficient (such as a one result being a subset of another).
  • All reactive data changes are queued into a component’s state with this.setState. No this.data just the usual this.state.
  • You may selectively depend on state and props changes (or not), meaning you can check to see if a specific prop changed in a certain way before rerunning a computation and skip rerunning it if it doesn’t pass. Think shouldComponentUpdate but for each of the reactive computations. You can also set your computation to only rerun on a props change (and of course if its dependencies trigger).

Quick Usage

meteor add huttonr:react-meteor-computations

The basic structure then is:

TestMessage = React.createClass({
  mixins: [ReactMeteorComputations],

  computations: {
    message() {
      return Session.get('lastMessage')
    },
    
    name() {
      return Session.get('playerName')
    }
  },

  render() {
    return (
      <div>
        <span>Name: {this.state.name}</span>
        <br/>
        <span>Message: {this.state.message}</span>
      </div>
    )
  }
})

Some Advanced Usage

computations: {
  _messages(dep, ret) {
    dep.state((partialState) => (partialState.limit !== undefined)) // Think of this as a setState hook
    dep.props((nextProps) => (this.props.date !== nextProps.date))  // Think of this as a willReceiveProps hook
  
    let messages = Messages.find({user: this.props.user}, {sort: {date: 1}, limit: 100}).fetch()

    let unreadMessageCount = 0
    messages.forEach((doc) => {
      if (doc.unread) unreadMessageCount++
    })

    return ret({messages, unreadMessageCount})
  }
},

...
3 Likes

Interesting! I like the idea of separate computations producing each value.

Is there a reason that you went with a mixins, rather than composition?

I think by using composition, you don’t have to monkey around the lack of a componentWillReceiveState, because state won’t be allowed in the computation.

How do you think your dep.state() & dep.props() syntax compares to something like cursors in reselect?


Just been playing with this idea, https://gist.github.com/nathan-muir/415f0167a091f80b1fda

@nathan_muir

Thanks for the feedback!

Though mixins do seem like they may potentially be on the way out, I was looking to replace ReactMeteorData in a project I was working on. I had looked into other options such as Flux, Redux, etc. but I did not like how these interfaced with Tracker and each of these felt so cumbersome in comparison with usual Meteor/Tracker or usual React. So that left me with two options: a) Work out a method to integrate Tracker into React components with the ease and simplicity of ReactMeteorData but with the efficiency of some of the alternatives, or b) Ditch Tracker almost entirely and go full Flux or Redux or some such.

The problem I ran into with option (b) was how convoluted code became that should have been simple. I mean that’s the beauty of Meteor and Tracker, right? It should be something as simple as the Quick Usage example above.

So that left me with option (a), to essentially redesign ReactMeteorData and hopefully solve some of the issues I was having with it (as I listed in the Introduction above).

Now it’s true that I could have still gone with composition, however the connection component in a composition can’t access the state of its child. And while it may seem like bad practice to base data on state, it was the least convoluted solution in this case.

So that’s why I chose mixins over composition. It fit my use case better and ended with simpler, less convoluted and if I may, more “Meteor-like” code.

In terms of other selector libraries such as reselect (as you listed), the real reason these more intricate libraries are not used for dep.state() and dep.props() is due to dep.props() being modeled directly after componentWillReceiveProps. In fact, it is actually a hook on componentWillReceiveProps, hence the single parameter: nextProps. However dep.state() is actually a hook on setState() and replaceState() which take the parameters: partialState and nextState respectively. And so it would follow that dep.state() would take the parameter partialState so you could check if there was a change being made to a particular part of state.

I found the simplicity of dep.props((nextProps) => (this.props.date !== nextProps.date)) to win out over other solutions.


I love your gist post. Great example of using composition and selectors to accomplish something similar to ReactMeteorComputations. It’s just the “overhead” code that kills me.

Though, I do think that is definitely the right direction to head as far as non-mixin solutions.

I really appreciate your feedback and please feel free to provide any further feedback you may want to share.