Immutable, React and Bacon

In an attempt to bring some of the benefits of Elm to React, you can use
Immutable JS to ensure that all state is immutable and Bacon JS to enable
streams of events to handle the controllers. This approach differs slightly from
the Elm Architecture approach in that the controllers are completely removed
from the views (this approach doesn’t need a single onClick or similiar
attribute on any DOM elements).

Elm’s approach to UIs is becoming increasingly popular for it’s simplicity,
clarity, and reliability (although the excellent type system helps somewhat here).
In particular, having the state in a single immutable data-structure helps to
avoid trippiing over yourself as the state changes as you no longer have to
worry about the order in time that changes have been made to reason about what
the result should be. Reactive events (Streams in Bacon JS and Signals in Elm)
enable you to treat events as values in time that can be filtered to create new streams
and mapped to alter the values. With the controllers handled like this, it’s simple
to understand what the outcome of various events should be. Finally using a reactive
DOM library (VDOM in the case of Elm) not only has perfomance benefits thanks to the
diffing of the changes that should be made to the DOM, but also provides a simple abstraction
layer that ties in nicely with the event streams to make the view layer simply another
transformation on the state (albeit one that has an effect that is rendered on the screen).

Models Views Controllers

The basic pattern is simply a variation on the classic MVC pattern.

Controllers

Starting with the controllers, the selectors for the views are stored together so that
they can be used by both the controllers and the views. A Bacon Event Stream
is created from events on the document that are then filtered by the ID or class
and mapped to a functor that performs an action on the previous state:

varSELECTORS={updateMessageFieldId:'update_message_field'}

// Eventsvarkeyups=Bacon.fromEvent(document,'keyup');

// ControllersvarupdateFieldEvents=keyups.filter(function(event){// Filter by the ID of the elementreturnevent.target.id===SELECTORS.updateMessageFieldId;}).map(function(event){// Return a functor to alter the previous statereturnfunction(previousState){returnpreviousState.set('message',event.target.value);};});// The main eventStream that merges all Controller streams into onevareventStream=Bacon.mergeMany([updateFieldEvents])

Models

The models define an initial state and a current state stream that is defined by
taking the eventStream and folding (eventStream.scan) it over the initial state
to perform the actions on it:

// Define the initial state of the appconstinitialState=I.fromJS({message:'Hello World!'});

constcurrentStateStream=eventStream.scan(initialState,function(previousState,eventAction){// Call the functor from the controller// to return a new altered version of the previous statereturneventAction(previousState);});

Views

Finally the currentStateStream is used to call Reacts render function to update
the DOM with the evaluation of the React views with the updated state:

currentStateStream.onValue(function(currentState){// Render the main React component on changes to the state to update the UIrender(<Appselectors={SELECTORS}state={currentState}/>, containerElement);});