React without React mixins


#1

I’ve been playing around with React for several months now and I’ve stumbled upon a different way of integrating with React that doesn’t use React mixins. Let me know what you think:

I spend quite a while building a mixin so I could use React with Meteor, but I’ve come to the conclusion that mixins are the wrong way to integrate with Meteor. In building this mixin, things started to get messy and eventually I found myself fighting two frameworks instead of one! The solution is to think more functional as opposed to object oriented.

First, think of React as a rendering engine rather than a view controller. Create a top-level App component that takes in an immutable object representing the state of the entire application and renders the whole app based on that state. Now all your time can be focused on how you change the state and render again. Simple as that. And this, I believe, is how React was intended to be used – every component should be using the PureRenderMixin.

Here’s how things end up working. When you want change the view, you “evolve” the state. Evolve is the term I use for both extending, and applying functions on key-values. The other thing you’ve heard a bunch about React is immutability. So using a library like Ramda is very convenient. To set the “route” prop of you top level component to ‘home’ and render it, simply do this:

evolveState({ route: 'home' })
render()

The top level React component gets this object as props and renders the whole page for you. Now, you have lots of freedom with how you structure the state of your the app.

Suppose you have data in your app, and your state looks like this:

State = 
  route: 'home'
  home: 
    posts: [{_id:1, title:"yay"}]

If theres a new event you want to render (perhaps in a callback from Cursor.observe.added), you could do this:

evolveState
  home:
    posts: append(doc)
render()

Here, append is Ramda’s append function which is curried so it will return a function. And this function will be appended to end of the posts lists in an immutable fashion. And just like that, you can build your app. This has a very clean separation of view from controller. And the best part is, theres barely any code!

If Meteor wants to support the React and whatever the next hottest front-end framework, I suggest going this route. It requires far less commitment to how Meteor integrates with the view. Anyways, here the code I use in all my react apps. Its not really package-worthy. Its just a pattern.

# create a top-level object to hold all your React components
Views = {}

# the top-level state of the app
State = {}

# shallow clones an object
shallowClone = (obj) ->
  newObj = {}
  for key, value of obj
    newObj[key] = value
  return newObj

# Whatever changes must have a new reference, everything else
# has the same reference to optimize for React with PureRender 
# which checks object reference equality. I figured this out from
# omnicient.js: http://omniscientjs.github.io
evolve = curry (dest, obj) ->
  newDest = shallowClone(dest)
  for k,v of obj
    if isPlainObject(v)
      newDest[k] = evolve(newDest[k], v)
    else if isFunction(v)
      newDest[k] = v(newDest[k])
    else
      newDest[k] = v
  return newDest

# Similar to evolve but uses a predicate over an array
evolveWhere = curry (pred, evolution, list) ->
  map((element) ->
    if pred(element) then evolve(element, evolution) else element
  , list)

# This is a main function we use that has side-effects
evolveState = (evolution) ->
  State = evolve(State, evolution)

# I like to use coffeescript instead of JSX.
createView = compose(React.createFactory, React.createClass)

# The top-level React component. `State.route` determines which view to
# render with props in `State[State.route]`. 
App = createView
  displayName: 'App'
  mixins: [React.addons.PureRenderMixin]
  render: ->
    route = this.props.route
    view = Views[route]
    props = this.props[route]
    view(props)

# render the top-level component with the app state
render = (done) ->
  React.render(App(State), document.body, done)

Thats basically it. I’ve been SO much happier working with React since I started using it in this way. Anyways, I’d love to hear what you think and how you’re integrating React with Meteor.


#2

Can you elaborate on “In building this mixin, things started to get messy and eventually I found myself fighting two frameworks instead of one!”?

I haven’t run into this problem and the mixin approach is working for me - what am I missing?


#3

Managing reactivity, showing loading animations while subscriptions are loading…


#4

Interesting approach. But, I’m quite not using this 100% is the best way to look at it. But I got a point from this.

Rather than using separate state object, we can make our all mini-mongo collections as the state.

May be we can use https://github.com/omniscientjs/immstruct behind the scene. That means maintain a immutablejs supported API for minimongo cache on the client. So, we can use PureRenderMixin also to gain performance.

Then once we got the data from the server, we can simply call the render function.

I think that’ll be pretty awesome and user does not need to do any kind of work to integrate with Meteor.


#5

Mixins aren’t supported in ES6 ES7 classes and a different composable approch have been suggested
What do you think about it?


#6

@grigio, he’s totally right. Functional composability is king.

In my experiences, I’ve come to learn that following functional programming patterns (like composing) offers much more benefits than object-oriented patterns (like mixins).

I’m sure everyone has struggled with terribly annoying bugs due to a mutable array or object, or a side-effect of some function. If you ensure all functions are pure (as much as possible with as few exceptions as possible, like render and evolveState), and that all data is immutable, then you’re code will be so much easier to reason about.

On a more personal note, I love animating UI’s. One of the reasons I landed in this approach (and functional programming in general) is because its a lot easier to get in-between functions and run animations. For example, its really easy now to do something like this:

animation = (done) ->
  animateOut -> 
    evolveState({feed: {posts}})
    render -> 
      animateIn(done)

Whereas with Meteor’s reactivity, its really hard to get in between the all the Cursor reactivity with async functions. Even with _uihooks, you’re limited with what you can do. I’ve actually created a class to abstract away subscriptions and cursors so I can be more functional with them:

    sub = createSubscription
      subscribe: (listId, onReady) ->
        Meteor.subscribe('list', listId, onReady)
      startLoading: () ->
      stopLoading: () ->
      cursors:
        items: 
          cursor: (listId) -> Items.find({listId}, {sort: {title: -1}})
          initial: (docs) ->
          addedBefore: (doc, beforeId) ->
          changed: (id, fields) ->
          removed: (id) ->
        list:
          cursor: (listId) -> Lists.find(listId, {limit:1})
          initial: (docs) ->
          changed: (id, fields) ->
          removed: (id) ->

This way, I can explicitly call evolveState and render with whatever animations I want including nice staggered animations on the initial set of documents (something you cannot do easily in blaze).


#7

I just want to chime in and say that I’m really excited about conversations like this. We want people to explore different ways to manage data, which is why we made it easy to add all of the different components of the new integration separately, so that you can avoid the mixin if you want to.


#8

Another question, this seems to be a duplicate of the thread on GitHub: https://github.com/meteor/react-packages/issues/19

Is it better to discuss here or there?


#9

Is this anything to worry about?

Are React mixins going away? And if so, how will this affect React within Meteor?


#10

There’s a long discussion about mixins in the react guide explaining why we chose that approach - I don’t think any of the facts have changed!

Either way, it would be simple to build a higher order component library using the mixin.


#11

You can put the mixin code in a es6 class and then just extend that class instead of React.Component when you need access to getMeteorData…

class MeteorData extends React.Component {
    (mixin code goes here)
}

Then when you create a component you do it like this:

class MyComponent extends MeteorData {
    getMeteorData() {
    }
    render() {
    }
}