How to create a universal ReactJS application with Flux
Erstwhile in the series
In the last post we created a simple application, using just bare React.
Full code of the application is accessible here.
The important thing to notice is that we hold the state of the app in many places. In a more complicated application it can cause a lot of pain :)
In this post we will update our app to use a more structured pattern for managing the state – Flux.
Why Flux?
Using bare ReactJS was easy, but our application is simple. With lots of components, having the state distributed all over them would be really tricky to handle.
Facebook experienced such problems, from which a very well known one was the notification bug.
The bug was that you saw the notification icon indicating that you have unread messages, but when you clicked the button to read them, it turned out that there’s actually nothing new.
This bug was very frustrating both for users and for Facebook developers, as it came back every time developers thought they already fixed it.
Finally, they realized that it’s because it’s really hard to track updates of the application state. They have models holding the state and passing it to the views, where all the interactions happen. Because of this, it could happen that triggering a change in one model caused a change in the other model and it was hard to track how far to other models these dependencies go.
Summing up, this kind of data flow is really hard to debug and maintain, so they decided they need to change the architecture completely.
So they designed Flux.
General idea
First of all, you need to have in mind that Flux is an architecture, an idea. There are many implementations of this idea (including the Facebook one), but remember that it’s all about the concept behind them.
And the concept is to have all the data being modified in stores.
Every interaction that causes change in the application state needs to follow this pattern:
- create an action – you can think about it as a message with a payload
- dispatch the action to the stores using a dispatcher (important: all stores get the message)
- in the view, get the store state and update your local state causing the view to rerender
You can have many stores and there is a mechanism to synchronise modifications done by them if you need it.
I recommend that you read a cartoon guide to Flux, the architecture is explained really well there, and the pictures are so cute! :)
Smart and dumb components
A thing worth emphasising is that some components will require their own state. We will call them “smart components”. Others, responsible only for displaying the data and attaching hooks, we could call “dumb components”.
“Smart components” don’t modify their state by themselves – like I mentioned earlier, every state change is done by dispatching an action. They just update their state by using a store’s public getter.
“Dumb components” get the state by passing needed items through props.
Let’s fluxify our app
Let’s add new dependencies to our package.json by running: npm install –save flux events.
Dispatcher
As I said, all state changes need to be done by dispatching actions. We need to create src/AppDispatcher.js then:
Action types
It’s good to have all action types defined in one file. Create a src/constants directory with ActionTypes.js inside:
Action creators
Now we will define the SubmissionActionsCreator:
SubmissionActionsCreator uses AppDispatcher to dispatch needed actions.
As you can see, an action is just a simple Javascript object with data that the store will need to calculate the state change.
An important key that will be always present in action object is actionType – one of the constants listed in the ActionTypes.js file.
Here we also need the submission id and sometimes rate.
Now we can update our smart SubmissionPage component to use SubmissionActionsCreator instead of just directly accessing the API:
Store
And the last thing we need is to add the store where our state will live:
- getSubmission – a public getter that we will use in our smart component to update its local state based on store state
- addChangeListener – an interface for subscribing for store state change
- removeChangeListener – an interface for unsubscribing for store state change
- emitChange – a private store method for notifying about store state change
Notice also the AppDispatcher.register part, where we do the actual request to the API, update the store state on success and notify all subscribed components that the state has changed.
Now we can update our smart SubmissionPage component to use SubmissionStore.
The whole SubmissionPage class should look like this:
In componentDidMount we use SubmissionActionsCreator to dispatch requestSubmission.
Because in componentWillMount we subscribe for store change using addChangeListener, we will be notified when the submission is loaded from the API.
Remember to unsubscribe in componentWillUnmount.
Thanks to the subscription, the onChange method will be called on store state change. And in onChange method we can update the local state to the current store state then.
Exactly the same mechism is used in performRating.
That’s all!
We updated our application to use the Flux architecture. It’s definitely an improvement over using bare ReactJS. We have more control over the application state.
But it has some downsides too. If the application grows and there are a lot of stores it’s hard to synchronize changes, especially when the stores depend on each other.
I will write more about this in the next post, where we’ll introduce Redux to our application.
For now, you can practise a bit by fluxifying the rest of the application.
Full code accessible here.
See you next week!