Why You Should Care About Flux

If you have not been developing using Flux yet I would strongly urge you to check it out.

As I’ve discussed before, the ideas behind Flux are not new. However, its application
to frontend architecture has had a significant impact on developers, myself included.

So why should you care about Flux?

The benefit of Flux is that it makes your code much easier to reason about. It achieves this by:

Separating the rendering of a component from its interactions.

Eliminating local states in favour of shared states.

Separating Reads From Writes

We are all humans, which means we have limited mental capacity. This is why we break bigger problems into smaller ones; Create
smallers modules from bigger ones.

In Rich Hickey’s talk, Simplicity Matters, he talks about how making things
easy means bringing it near to our capabilities. In this sense, we need to break problems down to a size that can
fit in our heads.

In Flux, we can partition the functions of a component into two distinct groups:

Reading from the system and rendering.

Capturing user interactions and writing to the system.

We must never complect these two groups together.

This makes our component much simpler. Its rendering is only affected by Stores, and user interactions
are mapped to the corresponding Actions. The two responsibilities do not directly affect one another.

Take the following React example of a top navigation user link.

classTopNavUserLinkextendsReact.Component{constructor(props){super(props);UserStore.addListener('change',this.updateUser);}render(){return(<div>Hello{this.state.user.name}!<aonClick={this.signOut}>Signout</a>
</div>);
}// Causes render() to be called.updateUser(){this.setState({user:UserStore.getUser()});}// An action is created, which eventually will cause a UserStore change.signOut(){UserActions.signOut(this.state.user);}}

This example shows that rendering is only affected by the UserStore. And signing out simply means calling UserActons.signOut.
We don’t directly mutate the state of the component after signing out. The state will eventually be updated when UserStore
updates, or potentially something may cause the store to not update. We don’t care!

Implications

The implications are that if we have a rendering problem, then we only need to check two things:

That our component’s render() method is correct based on what it gets from the Store.

That the Store is updating correctly based on Actions dispatched. e.g Store projects Actions to the correct state.

If we have a user interaction bug, then we only need to check.

That our component is passing the correct data to its callbacks.

That the Action (or ActionCreator) handles the data correctly.

Up to Eleven

Let’s take this concept even further by eliminating component states completely.

In React, we can achieve this by wrapping our component within a container.

// We introduce a container that interacts with Stores and ActionsclassTopNavContainerextendsReact.Component{constructor(props){super(props);UserStore.addListener('change',()=>{this.setState({user:UserStore.getUser()});});}render(){return<TopNavUserLinkuser={this.state.user}onSignOut={this.signOut}/>
}signOut(user){UserActions.signOut(user);}}// Our component then becomes even simplerclassTopNavUserLinkextendsReact.Component{staticpropTypes={user:object,onSignOut:func}render(){return(<div>Hello{this.props.user.name}!<aonClick={this.onSignOut}>Signout</a>
</div>);
}onSignOut(){this.props.onSignOut(this.props.user);}}

You may have noticed that our render function is more or less a referentially transparent function. That is, it maps some
data to HTML. If we call the render with the same data, we will always get the same result. This is a very nice property!

As a bonus, we have also made our component much easier to test because we don’t have to account for state mutations.

Eliminating Local States

Having local states littered across multiple components can quickly get out of hand. Each mutable state
means yet another thing we have to keep in our head when trying to reason about our application.

Recall from our previous container example that we pushed component state onto the container. The container gets its state
from Stores, which are the sources of truth within Flux.

Practically, this means that all of our complex state mutations only happen within Stores.

There are some local states kept within containers, however these should only reflect what are inside Stores.

This refactoring is important because other containers that depend on the same data should be reading it from the store. Otherwise,
you’ll have to share the squaring logic amongst all the components, and make sure they all maintain the correct local state.

By pushing state further and further up, we simplify our application by decreasing the amount mutable states we have to keep track of.
Moreover, our business logic is pushed to a smaller number of objects – Stores as opposed to containers/components.

Up to Eleven

Even though we pushed the state up to Stores, a badly implemented container/component can still rain on our parade – perhaps unintentionally.

classUserContainerextendsReact.Component{// ...updateName(evt){const{user}=this.props;// This may unintentially mutate the Store state if this.props.data was passed// by reference from Store state.data.name=evt.target.value;// Even if this fails, the store's user name may have been updated already.// Oops, now our application may be in a bad state!UserActions.updateUser(data);}}

To guarantee that our Store state cannot be mutated outside of the Store, we can turn to Immutable-js.

classUserStoreextendsEventEmitter{constructor(){Dispatcher.on('USER_UPDATED',this.update);// The state cannot be mutated outside of this store.this.state=Immutable.Map({});}update({user}){this.state=this.state.set('user',user);}}

Wrap-up

In this post, we saw how adopting Flux can simplify our application.

Firstly, by separating reading from writing, we make our application much easier to reason about. For any rendering issue,
we only need to check Store logic and component rendering logic. For any interaction bugs, we only need to check our component
handlers and Action logic.

Secondly, by pushing state further up to the Stores, we decrease the amount of mutable state we need to keep track of within our
application.