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.