A practical understanding of Flux July 20, 2015 on Drew DeVault's blog

React.js and the Flux are shaping up to be some of the most important tools for web development in the coming years. The MVC model was strong on the server when we decided to take the frontend seriously, and it was shoehorned into the frontend since we didn’t know any better. React and Flux challenge that and I like where it’s going very much. That being said, it was very difficult for me to get into. I put together this blog post to serve as a more practical guide - the upstream documentation tells you a lot of concepts and expects you to put them together yourself. Hopefully at the end of this blog post you can confidently start writing things with React+Flux instead of reading brain-melting docs for a few hours like I did.

At the core of it, React and Flux are very simple and elegant. Far more simple than the voodoo sales pitch upstream would have you believe. To be clear, React is a framework-ish that lets you describe your UI through reusable components, and includes jsx for describing HTML elements directly in your JavaScript code. Flux is an optional architectural design philosophy that you can adopt to help structure your applications. I have been using Babel to compile my React+Flux work, which gives me ES6/ES7 support - I strongly suggest you do the same. This blog post assumes you’re doing so. For a crash course on ES6, read this entire page. Crash course for ES7 is omitted here for brevity, but click this if you’re interested.

Flux overview

Flux is based on a unidirectional data flow. The direction is: dispatcher ➜ stores ➜ views, and the data is actions. At the stores or views level, you can give actions to the dispatcher, which passes them down the line.

Let’s explain exactly what piece is, and how it fits in to your application. After this I’ll tell you some specific details and I have a starter kit prepared for you to grab as well.

Dispatcher

The dispatcher is very simple. Anything can register to receive a callback when an “action” happens. There is one dispatcher and one set of callbacks, and everything that registers for it will receive every action given to the dispatcher, and can do with this as it pleases. Generally speaking you will only have the stores listen to this. The kind of actions you will send along may look something like this:

Anything that would change data is going to be given to the dispatcher and passed along to the actions. Since everything receives every action you give to the dispatcher, you have to encode something into each action that describes what it’s for. I use objects that look something like this:

{
    "action": "STORE_NAME.ACTION_TYPE.ETC",
    ...
}

Where ... is whatever extra data you need to include (the ID of the record to fetch, the contents of the record to be added, the property that needs to change, etc). Here’s an example payload:

{
    "action": "ACCOUNTS.CREATE.USER",
    "username": "SirCmpwn",
    "email": "sir@cmpwn.com",
    "password": "hunter2"
}

The Accounts store is listening for actions that start with ACCOUNTS. and when it sees CREATE.USER, it knows a new user needs to be created with these details.

Stores

The stores just have ownership of data and handle any changes that happen to that data. When the data changes, they raise events that the views can subscribe to to let them know what’s up. There’s nothing magic going on here (I initially thought there was magic). Here’s a really simple store:

import Dispatcher from "whatever";

export class UserStore {
    constructor() {
        this._users = [];
        this.action = this.action.bind(this);
        Dispatcher.register(this.action);
    }

    get Users() {
        return this._users;
    }

    action(payload) {
        switch (payload.action) {
        case "ACCOUNTS.CREATE.USER":
            this._users.push({ 
                "username": payload.username,
                "email": payload.email,
                "password": payload.password
            });
            raiseChangeEvent(); // Exercise for the reader
            break;
        }
    }
}

let store = new UserStore();
export default new UserStore();

Yeah, that’s all there is to it. Each store should be a singleton. You use it like this:

import UserStore from "whatever/UserStore";

console.log(UserStore.Users);

UserStore.registerChangeEvent(() => {
    console.log(UserStore.Users); // This has changed now
});

Stores end up having a lot of boilerplate. I haven’t quite figured out the best way to address that yet.

Views

Views are react components. What makes React components interesting is that they re-render the whole thing when you call setState. If you want to change the way it appears on the page for any reason, a call to setState will need to happen. And here are the two circumstances under which they will change:

The first bullet here means that you can call setState to change view states, but not data. The second bullet is for when the data changes. When you change view states, this refers to things like “click button to reveal form”. When you change data, this refers to things like “a new record was created, show it”, or even “a single property of a record changed, show that change”.

Wrong way: you have a text box that updates the “name” of a record. When the user presses the “Apply” key, the view will re-render itself with the new name.

Right way: When you press “Apply”, the view sends an action to the dispatcher to apply the change. The relevant store picks up the action, applies the change to its own data store, and raises an event. Your view hears that event and re-renders itself.

Why bother?

Practical problems

Here are some problems I ran into, and the fluxy solution to each.

Need to load data async

You have a list of DNS records to show the user, but they’re hanging out on the server instead of in JavaScript objects. Here’s how you accomodate for this:

From fetchIfNecessary in the store, go do the request unless it’s in progress or done. On the view side, show a loading spinner or something if you get null. When the change event happens, whatever code set the state of your component initially will be re-run, and this time it won’t get null - deal with it appropriately (show the actual UI).

This works for more than things that are well-defined at dev time. If you need to, for example, fetch data for an arbitrary ID:

Batteries not included

Upstream, in terms of actual usable code, flux just gives you a dispatcher. You also need something to handle your events. This is easy to roll yourself, or you can grab one of a bazillion things online that will do it for you. There is also no base Store class for you, so make one of those. You should probably just include some shared code for raising events and consuming actions. Mine looks something like this:

class UserStore extends Store {
    constructor() {
        super("USER");
        this._users = [];
        super.action("CREATE.USER", this.userCreated);
    }

    userCreated(payload) {
        this._users.push(...);
        super.raiseChangeEvent();
    }

    get Users {
        return this._users;
    }
}

Do what works best for you.

Starter Kit

If you want something with the batteries in and a base to build from, I’ve got you covered. Head over to SirCmpwn/react-starter-kit on Github.

Conclusion

React and Flux are going to be big. This feels like the right way to build a frontend. Hopefully I saved you from all the headache I went through trying to “get” this stuff, and I hope it serves you well in the future. I’m going to be pushing pretty hard for this model at my new gig, so I may be writing more blog posts as I explore it in a large-scale application - stay tuned.

Articles from blogs I read Generated by openring

Status update, April 2024

Hi! The X.Org Foundation results are in, and I’m now officially part of the Board of Directors. I hope I can be of use to the community on more organizational issues! Speaking of which, I’ve spent quite a bit of time dealing with Code of Conduct matters latel…

via emersion April 16, 2024

M2dir: treating mails as files without going crazy

Sometime recently in the past I complained about Maildir. You can go read the post, but the executive summary is that I think Maildir uses an actively user-hostile directory structure and extremely convoluted filenames that do not convey any meaning at all. …

via blogfehler! April 15, 2024

Go Developer Survey 2024 H1 Results

What we learned from our 2024 H1 developer survey

via The Go Blog April 9, 2024