Flux for stupid people

Flux For Stupid People

TL;DRAs a stupid person, this is what I wish someone had told me when I struggled learning Flux. It's not straightforward, not well documented, and has many moving parts.

This is a follow-up toReactJS For Stupid People.

Should I Use Flux?

If your application deals withdynamic datathenyes, you should probably use Flux.

If your application is juststatic viewsthat don't share state, and you never save nor update data, thenno, Flux won't give you any benefit.

Why Flux?

Humor me for a moment, because Flux is a moderately complicated idea. Why add complexity?

90% of iOS applications aredata in a table view.The iOS toolkit has well defined views and data models that make application development easy.

On the Front End™ (HTML, Javascript, CSS), we don't even have that. Insteadwe have a big problem:No one knows how to structure a Front End™ application. I've worked in this industry for years, and "best practices" are never taught. Instead, "libraries" are taught. jQuery? Angular? Backbone? The real problem, data flow, still eludes us.

What is Flux?

Fluxis a word made up to describe "one way" data flow with very specific events and listeners. There's no official Flux library, but you'll need theFlux Dispatcher, and any Javascriptevent library.

Theofficial documentationis written like someone's stream of conciousness, and is a bad place to start. Once you get Flux in your head, though, it can help fill in the gaps.

Don't try to compare Flux to Model View Controller (MVC) architecture. Drawing parallels will only be confusing.

Let's dive in! I willexplain concepts in orderand build on them one at a time.

1. Your Views "Dispatch" "Actions"

A "dispatcher" is essentially an event system with some extra rules added. It broadcasts events and registers callbacks. There is only everone, global dispatcher. You should use the FacebookDispatcher Library. It's very easy to instantiate:

var AppDispatcher = new Dispatcher();

Let's say your application has a "new" button that adds an item to a list.

<button onClick={ this.createNewItem }>New Item</button>

What happens on click? Your viewdispatches a very specific "action", with the action name and new item data:

createNewItem: function( evt ) {

    AppDispatcher.dispatch({
        actionName: 'new-item',
        newItem: { name: 'Marco' } // example data
    });

}

2. Your "Store" Responds to Dispatched Actions

Like Flux, "store" is just a word Facebook made up. For our application, we need a specific collection of logic and data for the list. This describes our store. We'll call it a ListStore.

A store is a singleton, meaning you probably shouldn't declare it withnew. There's only one instance of each store across your application:

/ Single object representing list data and logic
var ListStore = {

    // Actual collection of model data
    items: []

};

Your store then responds to the dispatched action:

var ListStore = …

// Tell the dispatcher we want to listen for *any*
// dispatched events
AppDispatcher.register( function( payload ) {

    switch( payload.actionName ) {

        // Do we know how to handle this action?
        case 'new-item':

            // We get to mutate data!
            ListStore.items.push( payload.newItem );
            break;

    }

});

This is traditionally how Flux handles dispatch callbacks. Each payload contains an action name and data. A switch statement determines if the store should respond to the action, and mutates data if it knows how to handle that action type.

Key Concept:A store is not a model. A storecontainsmodels.

Key concept: A store is the only thing in your application that knows how to update data.This is the most important part of Flux.The action we dispatched doesn't know how to add or remove items.

If, for example, a different part of your application needed to keep track of some images and their metadata, you'd make another store, and call it ImageStore. A storerepresents a single "domain"of your application. If your application is large, the domains will probably be obvious to you already. If your application is small, you probably only need one store. Generally, there is one store for one model type.

Only your storesare allowed to register dispatcher callbacks! Your views should never callAppDispatcher.register. The dispatcher only exists to send messages from views to stores. Your views will respond to a different kind of event.

3. Your Store Emits a "Change" Event

We're almost there! Now that your data is definitely changed, we need to tell the world.

Your store emits anevent, butnot using the dispatcher.This is confusing, but it's the Flux way. Let's give our store the ability to trigger events. If you're usingMicroEvent.jsthis is easy:

MicroEvent.mixin( ListStore );

Then let's trigger our change event:

case 'new-item':

            ListStore.items.push( payload.newItem );

            // Tell the world we changed!
            ListStore.trigger( 'change' );

            break;

Key Concept: We don't pass the newest item when wetrigger. Our views only care that _something _changed. Let's keep following the data to understand why.

4. Your View Responds to the "Change" Event

Now we need to display the list. Our view willcompletely re-renderwhen the list changes. That's not a typo.

First, let's listen for thechangeevent from our ListStore when the component "mounts," which is when the component is first created:

componentDidMount: function() {  
    ListStore.bind( 'change', this.listChanged );
},

For simplicity's sake, we'll just callforceUpdate, which triggers a re-render.

listChanged: function() {  
    // Since the list changed, trigger a new render.
    this.forceUpdate();
},

Don't forget to clean up your event listeners when your component "unmounts," which is when it goes back to hell:

componentWillUnmount: function() {  
    ListStore.unbind( 'change', this.listChanged );
},

Now what? Let's look at our render function, which I've purposely saved for last.

render: function() {

    // Remember, ListStore is global!
    // There's no need to pass it around
    var items = ListStore.getAll();

    // Build list items markup by looping
    // over the entire list
    var itemHtml = items.map( function( listItem ) {

        // "key" is important, should be a unique
        // identifier for each list item
        return <li key={ listItem.id }>
            { listItem.name }
          </li>;

    });

    return <div>
        <ul>
            { itemHtml }
        </ul>

        <button onClick={ this.createNewItem }>New Item</button>

    </div>;
}

We've come full circle.When you add a new item, the viewdispatches_an_action, the store responds to that action, the store mutates data, the store_triggers_a change event, and the view responds to the change event by re-rendering.

But here's a problem: we'rere-rendering the entire viewevery time the list changes! Isn't that horribly inefficient?

Nope.

Sure, we'll call the render function again, and sure, all the code in the render function will be re-run. ButReact will only update the real DOM if your rendered output has changed.Yourrenderfunction is actually generating a "virtual DOM", which React compares to the previous output ofrender. If the two virtual DOMs are different, React will update the real DOM with only the difference.

Key Concept: When store data changes,your views shouldn't care if things were added, deleted, or modified.They should just re-render entirely. React's "virtual DOM" diff algorithm handles the heavy lifting of figuring out which real DOM nodes changed. This makes your life much simpler, and will lower your blood pressure.

One More Thing: What The Hell Is An "Action Creator"?

Remember, when we click our button, we dispatch a specific action:

AppDispatcher.dispatch({  
    eventName: 'new-item',
    newItem: { name: 'Samantha' }
});

Well, this can get pretty repetitious to type if many of your views need to dispatch this action. Plus, all of your views need to know the specific object format. That's lame. Flux suggests an abstraction, calledaction creators,

which just abstracts the above into a function.

ListActions = {

    add: function( item ) {
        AppDispatcher.dispatch({
            eventName: 'new-item',
            newItem: item
        });
    }

};

Now your view can just callListActions.add({ name: '...' });and not have to worry about dispatched object syntax.

Unanswered Questions

All Flux tells us how to do is manage data flow. Itdoesn't answerthese questions:

  • How do you load and save data to the server?
  • How do you handle communication between components with no common parent?
  • What events library should I use? Does it matter?
  • Why hasn't Facebook released all of this as a library?
  • Should I use a model layer like Backbone as the model in my store?

The answer to all of these questions is:have fun!

PS: Don't Use forceUpdate

I usedforceUpdatefor simplicty's sake. The correct way for your component to read store data is tocopy data into stateand read fromthis.statein therenderfunction. You can see how this works in theTodoMVC example.

When the component first loads,store data is copiedintostate. When the store updates,the data is re-copiedin its entirety. This is better, because internally,forceUpdateis synchronous, whilesetStateis more efficient.

That's It!

For additional resources, check out theExample Flux Applicationprovided by Facebook. Hopefully after this article, the files in thejs/folder will be easier to understand.

TheFlux Documentationcontains some helpful nuggets, buried deep in layers of poorly accessible writing.

For an example of organizing components with Flux, see the follow upReactJS Controller View Pattern.

results for ""

    No results matching ""