ReactJS – Flux with AltContainer

At my current employer,  I’ve been leading the team in our rich client side applications by using the ReactJS library.  ReactJS is very component oriented in design and has been quite flexible to use (as well as very fast!).

There are quite a few ways to architect out a ReactJS solution, as ReactJS is a ‘library’ vs. a ‘framework’.   Facebook recommends using the Flux Architecture when building ReactJS applications.   Keep in mind that ‘Flux’ is a pattern vs. another library or framework itself.  Many in the ReactJS community use Redux, and Redux is fine, however when looking for a solution for our projects we were quite pleased at the functionality that AltContainer provides.   Together with the react-router, it solves 90% of our project needs.

The core pieces to understanding of the Flux Architecture is to get a good understand of how it wants you handle your application view state.  Often times in applications we will retrieve data from the server, modify that data inside the view – and then resubmit that data back to the server.   Andrew Ray gives a good simple summary of what Flux is in his blog post ‘Flux for Stupid People‘ (quite a harsh title, but oh well!).   One of the keys is that data flows in one direction.  AltContainer provides a simple paragraph of Flux that should suffice for this article:

What is flux?

Flux is an application architecture for building complex user interfaces. It eschews MVC in favor of unidirectional data flow. What this means is that data enters through a single place (your actions) and then flow outward through their state manager (the store) and finally onto the view. The view can then restart the flow by calling other actions in response to user input.

(ref: http://alt.js.org/guide/ )

From here on out, I’m going to just describe how I built a screen that I thought best describes how ReactJS with Flux works, this article is not meant to be a walk-through tutorial.   (I recommend reading more on SurviveJS for a more tutorial deep dive).

An Example Workflow

Let’s take a look at the view I’ve built:

EditDoctor

This doesn’t look like much I know, but I’m going to focus on the ‘Facilities’ part of this screen.   In this application a doctor can have many facilities.  The data is brought down to the client, we fetch the initial data for the view by wiring up a call to our ‘Action’ in the react-router:   (by the way, I’m temporary using the hosted WordPress site and it appears it doesn’t support any code snippet formatting, so please bear with me!)

path:"edit-doctor/:doctorId", 
component:EditDoctor,
onEnter:next => AdminDoctorsActions.getDoctor(next.params.doctorId)

Already you can notice that we fetch our data through our ‘Actions’ – a core component of AltContainer.   Rather easy follow – when the path is matched, we load the ‘EditDoctor’ component with the data from our ‘getDoctor’ call.

Our ‘EditDoctor’ view is rather straight forward:

import {Component} from 'react';
import AltContainer from 'alt-container';
import ShowWhen from 'components/ShowWhen';

import AdminDoctorsStore from 'stores/AdminDoctorsStore';
import EditDoctorDetails from './EditDoctorDetails';

export default class EditDoctor extends Component {
    render() {
        return (
            <AltContainer store={AdminDoctorsStore}>
                <ShowWhen prop="doctor">
                    <ShowWhen prop="specialties">
                        <ShowWhen prop="facilities">
                            <EditDoctorDetails />
                        </ShowWhen>
                    </ShowWhen>
                </ShowWhen>
            </AltContainer>
        )
    }
}

Just to understand what is happening here – the AdminDoctorsAction fetches the data and dispatches to the store when the data is ready.

First we generate the actions

class AdminDoctorsActions{

    constructor(){
        this.generateActions(
            'getDoctorWithSpecialtiesSuccess'
        );
    }
export default alt.createActions(AdminDoctorsActions);

Then we create our action (simplifying the ajax call here…)

getDoctor(id) {
    return (...).
then(axios.spread((doctorsResponse, specialtiesResponse, 
facilitiesResponse) =>{
        this.getDoctorWithSpecialtiesSuccess({
            doctor: doctorsResponse.data,
            specialties: specialtiesResponse.data,
            facilities: facilitiesResponse.data
        });
    }));
}

The key piece here is that after the data is retrieved, we call the ‘getDoctorWithSpecialtiesSuccess’ – which dispatches the data (as you can see we are using Promises with our AltContainer).

Inside the store we bind to the actions as follows:

class AdminDoctorsStore {
    constructor() {
        this.bindActions(AdminDoctorsActions);
}

getDoctorWithSpecialtiesSuccess(data) {
    this.setState({
        doctor:data.doctor,
        specialties: data.specialties,
        facilities: data.facilities
    });
}
export default alt.createStore(AdminDoctorsStore, 'AdminDoctorsStore');

Rather straightforward right ?  The data is fetched by the action, it’s dispatched, the state is set inside the store via the setState call and the data is passed down to the view.  We have clear and clean separation of concerns.

So, let’s get back to the view for a moment, as we touched upon, we loaded the EditDoctor component, which wrapped our store state around a child component called ‘EditDoctorDetails’.  Here are the main pieces of that view (I’m using a few libraries for react-bootstrap, but I won’t go instead the details of that here):

<CheckboxGroup
    ref="cbg"
    value={_.map(this.props.doctor.facilities,
            (facility) => facility.id.toString())}
    onChange={this.facilitiesCheckboxChanged.bind(this)} >
    <label>Facilities</label>
    
{this.props.facilities.map(option => ... )}
</CheckboxGroup>

The state that is passed down to the view is now converted to readonly ‘props’ on the view.  This falls in line with the Flux concept of ‘one-way’ data.  The data is ‘bound’ to the view.  At first, coming to ReactJS from another library such as ‘Knockout’ or ‘Angular’ (I’ve used both extensively) is the initial ‘why do I need to use ‘map’ inside by view’.  Well, let me say, that quickly I found that using  javascript vs. some other expression library is much more straightforward – for now, you can think of the map as a ‘ng-repeat’.

The Checkbox list is able to bind the values from the model (this.props.doctor.facilities) and then all the checkboxes in the this.props.facilities.

Now, what happens when the user checks or unchecks any of the facilities ?  Keep in mind, you cannot alter ‘this.props’ – which is what you might first think you should do if you have a MVC background.  Another options might be to try to take the ‘props’ and then in the component constuctor, bind them to a state value.   You *could* do this, but then you would be missing out on the key features of Flux in my opinion.   So there is a dilemma, right ?   Let’s walk through the Flux/AltContainer way – and you’ll see that I do not have to alter ANY of the code for the view for this update!

As you notice, I am listening to changes made to the checkboxes here:

onChange={this.facilitiesCheckboxChanged.bind(this)}

Let’s take a look at the ‘facilitiesCheckboxChanged’ function:

facilitiesCheckboxChanged(e){
    AdminDoctorsActions.setFacilitiesFor(this.refs.cbg.getCheckedValues(), 
             this.props.facilities, this.props.doctor);
}

Yes… that is all there is to it  🙂  We are able to get all the currently checked items, along with the facilities and doctor, and send those back to the action to handle setting the model (again, this.props.doctor.facilities).

This time around is even simpler than when I fetched the data from the server, as I need an action defined, but the action does nothing but pass through to the store itself, so in this case all I need to do is update my action to include the bindings:

class AdminDoctorsActions{
    constructor(){
        this.generateActions(
            'getDoctorWithSpecialtiesSuccess',
            'setFacilitiesFor'
        );
    }

The action ‘setFacilitiesFor’ is generated automatically then, so now I just need the logic inside my store:

class AdminDoctorsStore {
setFacilitiesFor([facilityIds, facilities, doctor]){
    doctor.facilities = _.reduce(facilityIds, (facilitiesFor, id) => {
        let facility = _.find(facilities, {id:parseInt(id, 10)});
        if(facility){
            facilitiesFor.push(facility);
        }
        return facilitiesFor;
    }, []);

    this.setState({
        doctor: doctor
    })
}

Presto! – I set the doctor.facilities inside the store, which is handling my view state, make sure the collection has the correct values and the call setState on the doctor.   The data is then passed back to the view which re-renders it, showing the correctly checked facilities.

As you can see, once this pattern is understood, it provides tremendous power and flexibility.  Our team has really enjoyed working with ReactJS, and I attribute most of that to the ease in which you can create components and the joy of using the Flux architecture with AltContainer.

Thanks for take the time to read this article, if you have any questions or even areas of improvement, feel free to post your thoughts!

Until next time – happy ReactJS programming!