Introducing React-View-Model — MVVM with React

Chasen Le Hara by Chasen Le Hara

Introducing React-View-Model — MVVM with React

Chasen Le Hara Learn how to follow the Model-View-ViewModel pattern in React with a new package called React-View-Model!

posted in Open Source ,Development ,CanJS on September 27, 2017 by Chasen Le Hara

At Bitovi, we’ve built a few projects with React, and we like its straightforward API and using JSX for templates. Redux is common in the React community, and we know it’s not the right solution for every project.

For us, Redux doesn’t work well with our development workflow. We build apps as individual modlets that act as mini apps in and of themselves, that are assembled to make the larger app. Redux’s global store feels more like a monolithic meatball that controls the entire app.

Picture of meatball with straight spaghetti coming out from it

We believe independently-testable ViewModels makes for more maintainable code. Last but not least, implementing lifecycle methods such as shouldComponentUpdate in class components feels like a step backward because we’re used to CanJS intelligently updating the DOM when necessary.

To solve all of these problems, we decided to bring the best part of CanJS to React: its observables! Using react-view-model lets you create observable ViewModels for React components.

How to use react-view-model

Let’s look at an example of a functional React component that has a ViewModel.

First, we’ll import can-define to create an observable ViewModel:

import DefineMap from 'can-define/map/map';

export const ViewModel = DefineMap.extend({
  count: 'number',
  increment: function() {
    return this.count++;
  },
});

Each instance of our ViewModel type will have a count property and an increment method. Notice how this ViewModel type is separate from our component code; we’ll discuss testing this code on its own in a little bit.

Next, we’ll import react, react-dom, and react-view-model, then pass react-view-model the ViewModel type and a render function that uses the viewModel instance passed to it:

import ReactDOM from 'react-dom';
import reactViewModel from 'react-view-model';
import { ViewModel } from './view-model.js';

const AppComponent = reactViewModel(ViewModel, (viewModel) => {
  return (
    <div onClick={ () => viewModel.increment() }>
      Count: {viewModel.count} (Click Me)
    </div>
  );
});

Lastly, we’ll add our component to the DOM, which will create an instance of the view model and call the render function:

ReactDOM.render(
  <AppComponent count={0} />,
  document.body
);

With the component rendered to the DOM, we can click on the text and see the count increment. Whenever something that’s bound to in the view-model changes, the render function will be called again.

One of the beautiful things about this pattern is how our ViewModel is separated from the component code. This lets React focus on what it’s good at—the View—and give us the opportunity to create nice, testable instances of the ViewModel.

Testable ViewModels

With our ViewModel code separate from our component code, we can test it independently and reuse it across components.

import DefineMap from 'can-define/map/map';
import QUnit from 'qunitjs';
import { ViewModel } from './view-model.js';

QUnit.test('ViewModel increment', function( assert ) {
  const viewModel = new ViewModel({count: 1});
  assert.equal(viewModel.count, 1, 'Starting count is correct');
  viewModel.increment();
  assert.equal(viewModel.count, 2, 'Increment works');
});

In the example above, we have the same ViewModel as before, and now we can unit test its functionality without having to import React or render our component to the DOM.

Further reading

We’ve barely scratched the surface of what’s possible when CanJS’s observables are paired with React. The react-view-model documentation has more info about this powerful combination.

Let us know on Twitter, Gitter, or the CanJS forums if you experiment with this new package. We’re excited about the new possibilities and eager to hear about how you’re using them!

Create better web applications. We’ll help. Let’s work together.