How to create a universal ReactJS application with Flux

A tutorial how to create a universal ReactJS application with Flux.

#

Tech

#

Knowledge

#

Education

Anna Ślimak's avatar
Author:

Anna Ślimak

Erstwhile in the series

flux logo In the [last post](https://blog.lunarlogic.com/2015/how-to-create-a-universal-reactjs-application-with-bare-reactjs/) 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:

  1. create an action – you can think about it as a message with a payload
  2. dispatch the action to the stores using a dispatcher (important: all stores get the message)
  3. 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:

import { Dispatcher } from 'flux';
class AppDispatcher extends Dispatcher {
handleViewAction(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
})
}
}
const appDispatcher = new AppDispatcher();
export default appDispatcher;

Action types

It’s good to have all action types defined in one file. Create a src/constants directory with ActionTypes.js inside:

export default {
REQUEST_SUBMISSION: 'REQUEST_SUBMISSION',
PERFORM_RATING: 'PERFORM_RATING'
};

Action creators

Now we will define the SubmissionActionsCreator:

import ActionTypes from '../constants/ActionTypes';
import AppDispatcher from '../AppDispatcher';
const SubmissionActionsCreator = {
requestSubmission(id) {
AppDispatcher.handleViewAction({
actionType: ActionTypes.REQUEST_SUBMISSION,
id: id
})
},
performRating(id, rate) {
AppDispatcher.handleViewAction({
actionType: ActionTypes.PERFORM_RATING,
id: id,
rate: rate
})
}
}
export default 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:

// ...
import SubmissionActionsCreator from '../actions/SubmissionActionsCreator';
class SubmissionPage extends React.Component {
// ...
performRating(value) {
const id = this.state.submission.id;
SubmissionActionsCreator.performRating(id, value);
}
// ...
};
export default SubmissionPage;

Store

And the last thing we need is to add the store where our state will live:

import { EventEmitter } from 'events';
import {ReduceStore} from 'flux/utils';
import AppDispatcher from '../AppDispatcher';
import ActionTypes from '../constants/ActionTypes';
import Connection from '../lib/Connection';
const CHANGE_EVENT = 'change';
class SubmissionStore extends EventEmitter {
constructor() {
super();
this.submission = null;
}
getSubmission(submissionId) {
return this.submission;
}
addChangeListener(listener) {
this.addListener(CHANGE_EVENT, listener);
}
removeChangeListener(listener) {
this.removeListener(CHANGE_EVENT, listener);
}
emitChange() {
this.emit(CHANGE_EVENT);
}
}
const submissionStore = new SubmissionStore();
submissionStore.dispatchToken = AppDispatcher.register((payload) => {
let id;
switch (payload.action.actionType) {
case ActionTypes.REQUEST_SUBMISSION:
id = payload.action.id;
Connection.get(`/submissions/${id}`).then((response) => {
submissionStore.submission = response.data;
submissionStore.emitChange();
});
break;
case ActionTypes.PERFORM_RATING:
id = payload.action.id;
const rate = payload.action.rate;
Connection.post(`/submissions/${id}/rate`, { rate: rate }).then(
(response) => {
submissionStore.submission = response.data;
submissionStore.emitChange();
});
break;
default:
// Do nothing
}
});
export default submissionStore;
  • 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:

import React from 'react';
import Rate from './Rate';
import SubmissionStore from '../stores/SubmissionStore';
import SubmissionActionsCreator from '../actions/SubmissionActionsCreator';
class SubmissionPage extends React.Component {
constructor(props) {
super(props);
this.state = { submission: {} };
this._onChange = this.onChange.bind(this);
}
componentDidMount() {
const id = this.props.params.id;
SubmissionActionsCreator.requestSubmission(id);
}
componentWillMount() {
SubmissionStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
SubmissionStore.removeChangeListener(this._onChange);
}
onChange() {
this.setState({ submission: SubmissionStore.getSubmission() });
}
performRating(value) {
const id = this.state.submission.id;
SubmissionActionsCreator.performRating(id, value);
}
render() {
const submission = this.state.submission;
return (
<div>
<div className="submission">
<h2>Submission</h2>
<ul>
<li>Id: {submission.id}</li>
<li>First Name: {submission.first_name}</li>
<li>Last Name: {submission.last_name}</li>
</ul>
</div>
<Rate rate={submission.rate} performRating={this.performRating.bind(this)} />
</div>
)
}
};
export default SubmissionPage;

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!

Want To Discuss
Your Next Project?

Our team is excited to work with you and create something amazing together.

let's talk

let's talk

More articles

We don’t just build software, we understand your business context, challenges, and users needs so everything we create, benefits your business.

thumbnail image for blog post
plus sign
thumbnail image for blog post
plus sign
thumbnail image for blog post
plus sign
Arrow