Notes on Flux State Manager (in React)

For Intermediate React Developers

These notes examine the Flux state manager in the context of React.

1 : Why Flux?

Where did flux come from and why?

Flux is the name given to a software design pattern used by Facebook to replace the MVC (model, view, controller) software design pattern they had previously used.

Facebook's implementation of the MVC pattern resulted in a tangled data flow and unpredictable results when dealing with UI state.

Facebook responded to this predicament with the Flux pattern which aimed to replace the tangled MVC relationships between a Model and View with a new pattern that enforced a "unidirectional data flow". This basically just means that state (within stores) flows in one direction through a set of processes passing through a single gateway (i.e. the dispatcher).

2 : Flux is mostly a JavaScript pattern

The flux pattern is made up of four parts and rests on a pub-sub foundation.

According to Facebook the flux pattern consists of:

  • View(s) - i.e. React components
  • Action(s) - a labeled set of new values (labeled = an action type and new values = a payload)
  • Dispatcher - a gate way that notifies all stores of a specific (i.e. type of action) state update. Flux provides dispatcher.js to be used in your applications. The only part you need from Flux, to implement the Flux pattern, the rest of it is just JavaScript.
  • Store(s) - a store is just an object/class that stores state, operates on state through dispatcher registered action handlers and can broadcast state updates to views via a pub/sub event system (e.g. EventEmitter from node events).

I like to think that the fifth part to Flux is the event system that connects a view to the store and store to a view. The pub/sub system is used by the view to subscribe to a store(s) events and the store uses the event system to publish finished events to the view (e.g. a todo was added to the store notify all listeners the store changed). Flux does not offer a pub/sub. Any system can be used, but the EventEmitter system that comes with Node.js is recommended.

Study this high level diagram outlining the Flux pattern but don't focus too long on it yet (i.e. might only confuse you don't be afraid to move to the next section):

After studying the todos application in the next section return to this diagram and make sure the pattern being communicated is clearly understood.

3 : Exposing the Flux pattern through a todo app

This sections will build a todo applications using each part of the Flux pattern.

3.1 - Views

TodosApp.js


import React from "react";

import Todo from "../components/Todo";
import NewTodo from "../components/NewTodo";
import TodoStore from "../stores/todoStore";

export default class TodosApp extends React.Component {
  state = {
    todos: TodoStore.getAllTodos() // get current values from a store (i.e. the state)
  };

  componentDidMount() {
    // use pub/sub system to connect store changes to this component
    // only possible because store is inherits from EventEmitter
    // (i.e. store is wrapped with pub/sub system)
    TodoStore.on("change", this.getCurrentTodosThenSetState);
    // when store emits change event, call getCurrentTodosThenSetState()
  }

  componentWillUnmount() {
    // don't leave event handler around when component is gone
    TodoStore.removeListener("change", this.getCurrentTodosThenSetState);
  }
  
  // when the store changes call this function
  getCurrentTodosThenSetState = () => {
    this.setState({ todos: TodoStore.getAllTodos() });
  };

  render() {
    const { todos } = this.state;

    return (
      <div>
        <h5>Todos:</h5>
        <hr />
        <ul>
          {todos.length === 0
            ? "nada to do!"
            : todos.map(todo => {
                return <Todo key={todo.id} {...todo} />;
              })}
        </ul>
        <hr />
        <NewTodo />
      </div>
    );
  }
}

NewTodo.js


import React, { Component } from "react";

import { createTodo } from "../actions/todoActions";

class NewTodo extends Component {
  constructor() {
    super();
    this.state = {
      value: ""
    };
  }

  handleChange = event => {
    const value = event.target.value;
    this.setState({ value });
  };

  handleSubmit = event => {
    const { value } = this.state;
    event.preventDefault();

    if (value.trim() !== "") createTodo(value);

    this.setState({ value: "" });
  };

  render() {
    const { value } = this.state;

    return (
      <form className="NewItem" onSubmit={this.handleSubmit}>
        <input type="text" value={value} onChange={this.handleChange} />
        <input type="submit" value="create todo" />
      </form>
    );
  }
}

export default NewTodo;

          

Todo.js


import React from "react";

import { deleteTodo } from "../actions/todoActions";

export default class Todo extends React.Component {
  handleDelete = () => {
    deleteTodo(this.props.id);
  };
  render() {
    const { text } = this.props;

    return (
      <li>
        <span>{text}</span>
        <span> - </span>
        <button onClick={this.handleDelete}>Done</button>
      </li>
    );
  }
} 
          

3.2 - Dispatcher

dispatcher.js


import { Dispatcher } from "flux";

export default new Dispatcher;  
          

3.3 - Actions

todoActions.js


import dispatcher from "../dispatcher";

export function createTodo(todoText) {
  dispatcher.dispatch({
    type: "CREATE_TODO",
    payload: todoText
  });
}

export function deleteTodo(todoId) {
  dispatcher.dispatch({
    type: "DELETE_TODO",
    payload: todoId
  });
}
          

3.4 - Stores (including pub/sub from EventEmitter)

todoStore.js


import { EventEmitter } from "events";

import dispatcher from "../dispatcher";

// state, not in expoerted class, private
let _todos = [{ id: Date.now(), text: "Eat Lunch" }];

// the store is a class extended from EventEmitter
class TodoStore extends EventEmitter {
  // methods that operate on store, then emits change event to subscribers:
  createTodo(todoText) {
    const id = Date.now();
    _todos = [
      ..._todos,
      {
        id,
        text: todoText
      }
    ];

    this.emit("change"); //react component updates
  }

  deleteTodo(todoId) {
    _todos = _todos.filter(todo => todo.id !== todoId);
    this.emit("change"); // react component updates
  }

  // interface to get current state of the store
  getAllTodos() {
    return _todos;
  }
}

// instance of store
const store = new TodoStore();

// register handlers with dispatcher so calling .dispatch()
// will result in a store knowing which method to call
// based on action type

dispatcher.register(action => {
  switch (action.type) {
    case "CREATE_TODO": {
      store.createTodo(action.payload);
      break;
    }
    case "DELETE_TODO": {
      store.deleteTodo(action.payload);
      break;
    }
    default:
  }
});

export default store;

3.5 - Runnable Todo App

4 : Why not Flux?

Outlines why most people use Redux instead of Flux.

4.1 - Why Redux, if we have Flux

In short, nothing is wrong with Flux. It's rough edges have solutions and both Flux and Redux follow a unidirectional data flow. But, KISS always wins. And Redux simplifies the Flux pattern. Therefore, more people understand it and use it.

Flux offers:

  • Store contains state and change logic (must change state in store with custom logic)
  • Multiple stores
  • Flat disconnected stores (How do stores interact with each other? .waitFor() ?)
  • Singleton dispatcher (all stores register with dispatcher, action creators, called from react components dispatch to the dispatcher, all stores get dispatch, stores with action type respond)
  • React Components subscribe to stores (e.g. EventEmitter)
  • State is mutated

Redux simplifies with:

  • Store and change logic are separate (i.e. only reducer/reducer patter changes state)
  • Single Store (many reducers but one store)
  • Single store with hierarchial reducers
  • No dispatcher
  • No event pub/sub (i.e. EventEmitter). Container components utilize connect logic from react redux (i.e. react-redux) glue.
  • State is Immutable

References

These external resources have been used in the creation of these notes.