Choosing React: Flexibility in Simplicity

At JAKT, we are no strangers to the battle of the javascript frameworks, and when trying to see through the fog of war, we often rely on a pragmatic approach combined with our own internal and collective web-tooling-zeitgeist. Here is what led us to cut the cruft and choose React on a recent project...

The brief

Client X asked us to build a white label version of their product - an embeddable module that allows their customers to render a condensed version of a checkout system as a standalone unit wherever they drop the script.

How we built it was up to us, provided the outcome would be one bundled script they could host on their CDN (akin to a Google analytics script or others).

Requirements
  • 3 encapsulated navigable views
  • views to be non-intrusive (no url routing)
  • speed / a light footprint
  • reliable build system that outputs one .js file
First thoughts
  1. Vanilla js/jQuery
  2. Angular?
  3. React?

Sometimes clients approach us with a desired framework in mind. Others allow us free reign. When we have to decide on a framework for the product, our goal is the delivery of a discreet and reusable environment; a product that is the combination of a well architected and future resistant application, as well as one that provides the most seamless developer experience possible.

We tossed around a few ideas. Digging into Angular posed more questions than solid answers. Would it play nice with other scripts? What if they already had an Angular app? Angular's ui-router is in no way friendly enough to allow a simple sub-navigation solution without some hacking, and we did not want bother with the headache of maintaining $scope.currentView and ng-controller="xyz" and .directive() and .link() and .compile() and $timeout() yadda yadda...

Rather than risk the unknowns, we turned to the library that handles the V in MVC. React.

In terms of pure flexibility and potential customization, React was the clear winner. We could assemble our own components and compose them in an extensible and modular way. Isn't that the mythical unicorn of front end application development? With React, the dream is a reality.

Furthermore, we would implement the well established webpack module bundler along with ES6. Not only is ES6 the accepted standard, we use it exclusively at JAKT on current projects, as the many improvements in syntax allow us to write less boilerplate and ultimately write more efficient and more readable code.

Pro tip: if you are not using ES6 today, you are behind the times. It is not a fad, it is not coffeescript 2.0, it is the next javascript spec.

Shots fired!

shots fired

How we did it

Though only 3 main views, there is A LOT of state to be maintained in this application. Registration, login, selecting items, calculating fees, applying promo codes, validating selections, payments... all candidates for a potential messy web of state management.

One of the core principles of React is unidirectional data flow, meaning state is stored and maintained in one single atom and passed "top down" to components. Furthermore, React 0.14 introduced stateless components which are an abstraction of base React components that significantly reduce boilerplate code:

in well designed apps, these "pure" components should be the majority of your application and can be tied together by a few "smart" wrapper components that control your state and pass down needed data to the pure components. -egghead.io

Sounds trivial, but the payoff is huge! Initial experiments with react-router proved that navigation without hijacking the browser url was near impossible. Stepping back, we realized we could just store our current view in our application state and let our pure components render the entire application. Using react-redux makes this incredibly simple and effective, and the entire app gleans from this paradigm of top-down rendering.

Lets take a look at our simple navigation component that sits at the top level of our <App /> component, which demonstrates unidirectional data flow:

class App extends Component {

    //magic navigation
    isCurrentView(view) {
        return view === this.props.visibleView
    }

    render() {
        const { visibleView, changeView } = this.props

        //check state.visibleView and render the appropriate view:
        return (
            <div className="app-view-container">

                <Navigation visibleView={visibleView} changeView={changeView} />

                { this.isCurrentView('one') && <ViewOne {...this.props} /> }
                { this.isCurrentView('two') && <ViewTwo {...this.props} /> }
                { this.isCurrentView('three') && <ViewThree {...this.props} /> }

            </div>
        )
    }
}

const mapStateToProps = (state) => ({  
    visibleView: state.visibleView,
    currentUser: state.currentUser
})

const mapDispatchToProps = (dispatch) => ({  
    //can call in any sub view with this.props.changeView(view)
    changeView: (view) => {
        dispatch({type: CHANGE_VIEW, view})
    }
})

//just watch this entire series: https://egghead.io/lessons/javascript-redux-the-single-immutable-state-tree
export default connect(mapStateToProps, mapDispatchToProps)(App)  


Now in our reducer we can just listen for the CHANGE_VIEW action:

/** Constants or Actions for the root reducer to listen for */
import * as ACTIONS from './actions'

/** Initial state object: 
    important with redux! must initialize state with default object
    lets start our app on view 'one' by default */
const initialState = {  
    visibleView: 'one'
}

export default function (state = initialState, action) {

    switch (action.type) {

        case ACTIONS.CHANGE_VIEW:
            return Object.assign({}, state, { visibleView: action.view });

        default:
            return state
    }
}


Since we declared changeView as a prop in our <App /> component, referencing it in a child component gives us access to that method:

const EventBox = ({ changeView }) => (  
    <div onClick={() => changeView('two')}>
        ...
    </div>
)

The only way to mutate the state is to emit an action, an object describing what happened. - @gaearon

Now, anytime a new state object is returned in our reducer, React sees that change and propagates the new value down through our view hierarchy. There is no manual binding necessary, no $scope.watch() needed, just pure functions of props. This is a win for our application on all fronts: ease of maintainability, client expectations, and especially developer sanity.

Closing thoughts

In the realm of front end web application development, there is no shortage of debate over the "best" client side javascript frameworks. It can be comfortable to sway toward highly charged opinions, rhetorical clickbait, or get stuck in the middle. However, when timelines are short and deadlines loom, it is a critical skill to reason about desired functionality of the proposed product and choose the tool that will allow you, the developer, to execute the product in a manner that compliments your flow while exceeding client expectations.

This is why we choose React.