Log In

React and Flux


by Yariv Katz

React components have a built-in private state for each component. It is common that a state of a component needs to be shared among many components, is that state should still be private? Should we keep the state private and transfer the needed information among all our components? While that is still possible using component props or a service to transfer that information, sometimes it's usefull to look at certain information as a state of our entire application. Using flux to build our react application architecture, that is exactly what we can achieve. This lesson is an introduction to creating a react app using flux architecture.

What is Flux

Flux is an application architecture that facebook is using for their client side web application. Let's look at the diagram of the flux architecture and try to understand it.

Essentially flux is a predictable way to modify state of our app that resides in the Stores. Based on the state in the store, the view which in our case is our react component are presented in a certain way. The react components will issue actions, that will go to the dispatcher which the stores will register the changes in their state according to the incoming actions. With flux architecture we have a uni directional data flow that manage the store change in a predictable manner.

The best way to learn flux is to start coding. In this example we will create a simple todo application. A todo item is a string describing what needs to be done. We will have a react component with a form that the user can enter the todo item. When submitting the form we will add the todo item to the store. We will have another react component that displays the list of todo items.

Start by creating a new react application using the create-react-app.

> npx create-react-app react-flux-tutorial
> cd react-flux-tutorial

Install flux with npm with the following command:

> npm install flux --save

Dispatcher

The dispatcher is in charge of sending the action to the stores. We can only change our state with the dispatcher dispatch method. The dispatch method will get the action. The stores will descide how to change the state according to the actions that matters to them. The dispatcher will send the action to all the stores. In the src folder create a new file called: Dispatcher.js with the following:

import {Dispatcher} from 'flux';

const dispatcher = new Dispatcher();
export default dispatcher;

Actions

Actions describe what happens in our app. When an action happens the state should change in a certain way. The actions are simple objects with a key of type. That key should be unique between each actions and the stores will use that key to know what action happened and how the state should change. The other keys are extra data we send along with the action. The actions are usually wrapped in a function that will call the action and place is in the dispatcher dispatch method Let's create an action called add-todo that will signifies that the user want to add a new todo item. In the src folder create a new file called TodoActions.js with the following:

import Dispatcher from './Dispatcher';

export const ADD_TODO = 'ADD_TODO'; 

export function addTodo(todoItem) {
    Dispatcher.dispatch({
        type: ADD_TODO,
        payload: todoItem
    });
}

We create a single action creator that dispatch an add todo action.

Store

Our application state resides inside stores. Each store has a section of the state. The store will register with the dispatcher to be notified when an action happened. The store needs to decide how to change the partial state the store is in charge of when the action arrives. Stores are classes that extend ReduceStore and that means they will need to implement two methods:
- getInitialState - the initial part of the state that this store is in charge of.
- reduce - this method is basically a switch on the action types where in each case we return the new state.
The state has to be immutable so it is better to use a library like Immutable.js We will create a single instance of each store. To register the store with out dispatcher we will need to pass the dispatcher in the parent class constructor. Let's first install immutable js library. Using npm type:

> npm install immutable --save

In the src folder, create a new file called TodoStore.js that will hold the array of todo items.

import {ReduceStore} from 'flux/utils';
import Dispatcher from './Dispatcher';
import {List} from 'immutable';
import {ADD_TODO} from './TodoActions';

class TodoStore extends ReduceStore {
    constructor() {
        super(Dispatcher);
    }

    getInitialState() {
        return List([]);
    }

    reduce(state, action) {
        switch(action.type){
            case ADD_TODO:
                return state.push(action.payload)
            default:
                return state;
        }
    }
}

export default new TodoStore();

We initiated the state with immutable list from the immutable library. When we get the ADD_TODO action we are pushing the payload to the list to create a new list with the item added.

Controller view

A controller view is a view (react component in our case) that is connected to the state. The controller view will get the data from the stores and pass it to child view. This will create smart components that our connected to the state, and dumb component that are pure and get the data from the parent in their properties. In our todo example we will create two dumb components: TodoList, TodoForm. we will create a smart controller view component which will be our App component. Usually our app will contain several smart components and not just a single one wrapping the entire app like in our case. Let's start with creating the dumb components. In the src folder, create a folder called views. In that folder create a file called TodoList.js with the following code:

import React, {Component} from 'react';

export default class TodoList extends Component {
    render() {
        const {todos} = this.props;
        return (
            <ul>
                {
                    todos.map((item, index) => <li key={index}>{item}</li>)
                }
            </ul>
        )
    }
}

we will get from our smart parent component, the list of todos through the properties of the component. In the render we are iterating on the todo list and creating a ul li list of todo items.

Let's create the form component. In the views directory create a new file called TodoForm.js with the following:

import React, {Component} from 'react';

export default class TodoForm extends Component {
    state = {
        todo: ''
    }

    handleText = (event) => {
        this.setState({
            todo: event.target.value
        })
    }

    handleSubmit = (event) => {
        this.props.addTodo(this.state.todo);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <div>
                    <label>
                        Enter todo item:
                    </label>
                    <input 
                        type="text" 
                        name="todo" 
                        value={this.state.todo}
                        onChange={this.handleText} />
                </div>
                <div>
                    <button type="submit">Add Todo</button>
                </div>
            </form>
        )
    }
}

Our component will control the input using the state, and will handle the submit by activating an action we are getting through the props.

Let's create our smart component. We will make our App component a smart component. To achieve this we have to create two static methods, one is in charge of connecting to the stores we need, and the other in charge of calculating the state. Modify the App.js

import React, { Component } from 'react';
import TodoList from './views/TodoList';
import TodoForm from './views/TodoForm';
import {Container} from 'flux/utils';
import TodoStore from './TodoStore';
import {addTodo} from './TodoActions';

class App extends Component {
    static getStores() {
        return [
            TodoStore
        ]
    }

    static calculateState() {
        return {
            todos: TodoStore.getState(),
            addTodo
        }
    }

    render() {
        return (
        <div className="App">
            <TodoForm {...this.state} />
            <TodoList {...this.state} />
        </div>
        );
    }
}

export default Container.create(App);

Few things to note here:
- our smart component implement a static getStores which returns an array of the stores we are connected to
- we also implement a static method calculateState that will return a state object calculated from the stores state and actions.
- We are passing the state and action from the smart component to the properties of the children

Our app is connected to the state and actions, and our children components are using our state and actions. We can now launch our app and we should see that when submitting the form an item is added to the TodoStore and from their our TodoList child is updated with the new item.

Async actions

Let's try and make our app a bit more complex by creating an async action. Our server is located at the following url: https://nztodo.herokuapp.com/api/task/?format=json. Our server returns a list of todo tasks. We want our app to query the server and populate the todos state with the list from the server. When we are sending the request and the response is not back yet, we want to switch an isLoading flag in the flux state. Modify the TodoActions.js to the following:

import Dispatcher from './Dispatcher';

export const ADD_TODO = 'ADD_TODO'; 
export const SET_LOADING = 'SET_LOADING';
export const SET_TODOS = 'SET_TODOS';

export function addTodo(todoItem) {
    Dispatcher.dispatch({
        type: ADD_TODO,
        payload: todoItem
    });
}

export async function fetchTodos() {
    Dispatcher.dispatch({
        type: SET_LOADING,
        payload: true
    });
    const res = await fetch('https://nztodo.herokuapp.com/api/task/?format=json');
    const todos = await res.json();
    Dispatcher.dispatch({
        type: SET_LOADING,
        payload: false
    });
    Dispatcher.dispatch({
        type: SET_TODOS,
        payload: todos
    });
}

We added an async action creator. before we send the request we are dispatching a set loading action to true. When the result is back we are dispatching a set todos action with the todo list from the server, and we are also setting the load state back to false.

We will need to update our store to respond to our new actions. Modify the TodoStore.js with the following:

import {ReduceStore} from 'flux/utils';
import Dispatcher from './Dispatcher';
import {List, Map} from 'immutable';
import {ADD_TODO, SET_TODOS, SET_LOADING} from './TodoActions';

class TodoStore extends ReduceStore {
    constructor() {
        super(Dispatcher);
    }

    getInitialState() {
        return Map({
            todos: List([]),
            isLoading: false
        })
    }

    reduce(state, action) {
        switch(action.type){
            case ADD_TODO:
                return state.set(
                    'todos', 
                    state.get('todos').push({title: action.payload})
                )
            case SET_LOADING:
                return state.set('isLoading', action.payload);
            case SET_TODOS:
                return state.set(
                    'todos', 
                    List(action.payload)
                )
            default:
                return state;
        }
    }
}

export default new TodoStore();

Our state is changed a bit and is now a Map with a todos array and an isLoading flag. This means we need to change the getInitialState accordingly. Our reduce function should support the new actions we added, so it can accept an action to switch the loading flag and an action to place the todo list from the server.

We will create another dumb component that is in charge of displaying a loading text when the is loading flag is true. In the views folder, create a file called: Loading.js with the following:

import React from 'react';

export default function Loading(props) {
    if (props.isLoading) {
        return <h1>Loading...</h1>
    }
    return null;
}

Our app component will be in charge to call the action to fetch the todo list. Modify the App.js like so:

import React, { Component } from 'react';
import TodoList from './views/TodoList';
import TodoForm from './views/TodoForm';
import {Container} from 'flux/utils';
import TodoStore from './TodoStore';
import {addTodo, fetchTodos} from './TodoActions';
import Loading from './views/Loading';

class App extends Component {
    static getStores() {
        return [
            TodoStore
        ]
    }

    static calculateState() {
        return {
            ...TodoStore.getState().toObject(),
            addTodo,
            fetchTodos
        }
    }

    componentDidMount() {
        this.state.fetchTodos();
    }

    render() {
        return (
            <div className="App">
                <TodoForm {...this.state} />
                <TodoList {...this.state} />
                <Loading {...this.state} />
            </div>
        );
    }
}

export default Container.create(App);

Now our calculateState static method will grab the entire state object including the action we added. We will call the fetchTodos action on the componentDidMount method to start fetching from the server. We are also placing the Loading component below the list and passing the state with the isLoading flag to it.

Each item from the todo list is now an object that looks like this:

{"id":9662,"title":"w11","description":"NAAMA 22","group":"gdfgdf","when":"2019-11-01T15:02:00Z"}

This means our list of todos is now a bit different and is a list of objects. We need to modify the TodoList component to accept a list of todo object and in each object print the title. Modify the TodoList.js

import React, {Component} from 'react';

export default class TodoList extends Component {
    render() {
        const {todos} = this.props;
        return (
            <ul>
                {
                    todos.map((item, index) => <li key={index}>{item.title}</li>)
                }
            </ul>
        )
    }
}

Now we can launch our app again and we should see the list populated with todo items from the server.

Debugging

Is is often useful to be able to check our state whenever we want. This will help us debug our app, we want to be able to look at the state whenever we spot a bug in our app or want to add a test for a component. We need a tool that will display the state and the actions that happened. There is a dev tool for flux that allows you to do that. The dev tool is connecting to an extension called Redux dev tools and that extension can be found here We will install the extension in our browser and we will also need to install an npm package that connects our stores to that extension:

> npm install --save-dev remotedev

In the src folder create a file called devTools.js with the following:

import { connectViaExtension } from 'remotedev';

// Connect to the monitor
const remotedev = connectViaExtension();

export default remotedev;

In each of our stores we will have to signal the change for our remote dev. Modify the TodoStore.js as following:

import {ReduceStore} from 'flux/utils';
import Dispatcher from './Dispatcher';
import {List, Map} from 'immutable';
import {ADD_TODO, SET_TODOS, SET_LOADING} from './TodoActions';
import remotedev from './devTool';

class TodoStore extends ReduceStore {
    constructor() {
        super(Dispatcher);
    }

    getInitialState() {
        return Map({
            todos: List([]),
            isLoading: false
        })
    }

    reduce(state, action) {
        let newState;

        switch(action.type){
            case ADD_TODO:
                newState = state.set(
                    'todos', 
                    state.get('todos').push({title: action.payload})
                )
                break;
            case SET_LOADING:
                newState = state.set('isLoading', action.payload);
                break;
            case SET_TODOS:
                newState = state.set(
                    'todos', 
                    List(action.payload)
                )
                break;
            default:
                newState = state;                        
        }
        
        remotedev.send(action, newState);
        return newState;
    }
}

export default new TodoStore();

Notice that we are now saving the new state, and calling the send method of our devtool object with the action and the new state. When launching the app you should see the extension lighting up and clicking it you should manage to see the current state and actions that lead to this state.

Summary

Flux is a uni directional data flow application architecture. We hold our state in stores and the dispatcher will send actions to our stores. We can connect our state and actions to smart react components and pass the state and actions to the smart component children. Seems like Redux is in fashion and everybody states that redux is better then flux, personally I look at flux and redux as a different specie of the same animal. I find both Redux and flux awesome and convenient way to build a scalable app with react. My personal favorite is Redux, and not because I find it easier or better but rather because it seems to have a larger community with better middlewares and tools associated with it. Hope this lesson made it a bit clear regarding everything that surrounds flux. Happy coding...