The last time Hackerfall tried to access this page, it returned a not found error. A cached version of the page is below, or click here to continue anyway

Codrspace - A simpler web architecture using Flux, CSP, and FRP concepts by allenkim67

Flux has done a lot to simplify web development and this is mostly because it lets you think about your app in a more functional way. The key words youll hear are unidirectional flow which essentially means you have actions that modify your model data which updates your views: actions -> model -> view.

But when you look at the various implementations of flux its not very functional at all. Typically what you see is that actions, models, and views are each some kind of stateful event object. Each link in the chain has references to the previous link so that it can listen to it trigger an event. Models have references to actions to listen to its events. Views have references to models to listen its changes. IMO events are very complex, because it can be hard to trace the flow of logic between them.

Things would be much simpler if we could just use plain javascript objects as our data. And when an action occurs we simply update our model and pass it to a rendering function.

So Id like to show an example of how I write my apps which I think is much simpler.

var csp = require('js-csp');
var React = require('react');

var channel = csp.chan();
var root = document.querySelector('#content');

//MODEL
var model = {count: 0};

//VIEW
var View = React.createClass({
  render: function() {
    return (
      <div>
        <button onClick={this.clickHandler}>this is a button</button>
        <div>clicked {this.props.model.count} times</div>
      </div>
    );
  },
  
  clickHandler: function() {
    csp.go(function*(){
      yield csp.put(channel, {actionType: 'incr'});
    });
  }
});

//UPDATE
function update(model, action) {
  switch (action.actionType) {
    case 'incr':
      model.count += 1;
      return model;
  }
}

//RENDER LOOP
function renderLoop(model) {
  React.render(<View model={model}/>, root);

  csp.go(function*() {
    while (true) {
      var action = yield csp.take(channel);
      model = update(model, action);
      React.render(<View model={model}/>, root);
    }
  });
}

renderLoop(model);

First I'll start with the model. You'll notice it's a plain javascript object. I think ideally you would use an immutable map, but I'll use a plain object for familiarity's sake. What I DON'T want is a traditional flux store which you have to register callbacks on with a dispatcher, and also acts as an event emitter to trigger rerenders on views.

Now if we look at the view, it's a fairly basic react component. But there's two things to notice. One is that I only have a render function and a click handler function. I want my views to be stateless, putting all my state in the model, and then I want to pass the model in from the outside as a prop. I don't want my views to have any internal state or references to external objects. I basically just want to be able to call

 React.render(<View model={model}/>, root)

at any time while having full control of what gets rendered simply by changing the data in the model, where the model is nothing more than a plain javascript object. You'll see fully how this is all possible in the render loop.

But before that let's look at the other interesting part of this code, which is the click handler. You can see that it uses a csp channel. Basically every action in my app will look something like this, where I make an action object (also a plain javascript object) and put it into the channel. Action objects have a type and an optional payload (no payload in the above code), looking something like this {actionType: 'action name', payload: {data: []}}. Also, all views share the same channel. This means that any and all possible user interaction will get funneled into this channel, represented as a plain javascript object. You can then receive those actions at the other end of the channel, and I only do so in one place: the render loop. So let's take a look at the render loop now.

First in the render loop I do an initial render passing in the initial data that the model starts off with. Again this is always passed in from the outside as a prop. Then we enter the loop which consists of three parts.

  1. We take an action from the channel. These were passed down from the views, they come through in chronological order. Again these are plain javascript objects with a type property and an optional payload.
  2. We pass the action and the current model into an update function to get back an updated model. The update function is essentially a big switch statement that updates the model in different ways depending on what the action type is (although in the example code there is only one switch case, there can be any number of cases).
  3. We rerender the view by passing in the updated model to our view component.

And that's it. The render loop loops forever, waiting for new actions, updating the model, and rerending the view. These 3 lines in the loop essentially represent the unidirectional flow of the flux model. I think by reducing the amount of events and using plain javascript objects we can simplify and more clearly reveal the intent behind the architecture.

Continue reading on codrspace.com