Step 1: F.lux Store and Basic Todo Functionality

Overview

This step adds state to the application using the default f.lux Store behavior. Minimal work is required to create an f.lux shadow state to have a data flow based application using immutable data structures without losing the simplicity of natural javascript idioms.

Goals

  1. Create a f.lux Store with initial state
  2. Subscribe to store changes in <Todos>
  3. Implement <AddTodo> addTodo() function
  4. Implement <TodoItem> to display, edit, and delete todo items
  5. Iterate all store todo items and create a <TodoItem> for each one

The examples/tutorial/step-1 directory contains the completed code for this step.

Technical background

F.lux stores all application state in a single object tree. A single source of truth greatly simplifies application state management, reduces complexity, and eases debugging. The f.lux store efficiently virtualizes the state tree into a shadow state as inspired by React’s virtual DOM. The virtualization process, called shadowing, binds action-type functions onto the state. Binding functions with the state makes explicit the operations that may be performed on a state property. Shadowing is recursively applied to the entire state tree. For performance reasons, the process occurs on a just-in-time basis so only the accessed properties are virtualized.

F.lux uses the Store class for representing stores. The following image shows how the store exposes the root state and the root property. Inside the root property is its shadow, which contains a reference to the actual state.

F.lux Architecture

The application works with shadow objects that are accessed just like traditional javascript objects. This means your application logic looks “normal” and you can interact and inspect state objects in the javascript console like regular objects. The shadow objects are immutable so interacting with them through property assignments and function invocation result in actions being dispatched to the store for in order processing. The store then generates a change event on the next tick for the application to process the new state.

The next image shows how a list of one todo item would be shadowed:

Shadowed Todos

The types of properties are shown in dark gray box titles. Out of the box, f.lux will recursively ‘autoshadow’ the state to automatically map Property subclasses onto the application state. The next step will cover specifying mapping instructions. Autoshadowing performs the following javascript type to f.lux type mapping:

Javascript type f.lux Property subclass API
Array.isArray(type) ArrayProperty Array
lodash.isPlainObject(type) MapProperty Map
all others PrimitiveProperty exposes the actual value

F.lux enables normal Javascript coding syntax to access and manipulate the shadow state in a Flux compatible way. The shadow state is immutable so all mutations, whether through assignment or functions/methods will generate f.lux actions that will change the actual state and generate store change notifications to registered listeners. Some examples of code used and described in the following sections to get the concepts flowing:

todos.map( t => <TodoItem todo={ t } todos={ todos } /> )
<input type="text"
    onChange={ event => todo.desc = event.target.value }
    defaultValue={ todo.desc }
/>
export default class TodoItem extends Component {
    removeTodo() {
        const { todo, todos } = this.props;
        const idx = todos.indexOf(todo);

        if (idx !== -1) {
            todos.remove(idx);
        }
    }


    render() {
        ...
    }
}

1. Create a f.lux Store with initial state

Add the following lines to main.js to create the application’s f.lux store:

import {
    Store,
    ObjectProperty
} from "f.lux";


// create the store and starting state
const root = new ObjectProperty();
const state = { todos: [] }
const store = new Store(root, state);

A f.lux based application uses a single store for managing all state. A Store instance is created by specifying a root property and the initial state. The property must be a Property subclass and the state a JSON compatible value of appropriate type for the root Property. In this case, an ObjectProperty instance is used to shadow, or proxy, a javascript literal object representing the application state. ObjectProperty is used to represent javascript literal objects and does not expose the rich Map api for adding and removing properties. ObjectProperty is useful when you have a well-defined object that does not require the functionality of a flexible, changing property list.

Drilling down into the initial state and adding an initial todo item:

{
    todos: [
        { desc: "Go skiing!", completed: false }
    ]
}

Autoshadowing will result with the following shadow property mapping:

The final change to main.js is to pass the store to the <Todos> component using:

ReactDOM.render(
    <Todos store={ store }/>,
    document.getElementById('react-ui')
);

2. Subscribe to store changes in <Todos>

A React component subscribes with the store to be notified when the state changes as a result of f.lux actions. The store exposes the following methods for this purpose:

The callback parameter has the following signature:

callback(store, shadow, prevShadow)

The top level <Todos> React component will be used to subscribe to state changes.

class Todos extends Component {
    componentWillMount() {
        const { store } = this.props;

        // bind onStateChange callback so can use it to register/unregister
        this.onStateChangeCallback = this.onStateChange.bind(this);

        // register for store change notificiations
        store.subscribe(this.onStateChangeCallback);
    }

    componentWillUnmount() {
        const { store } = this.props;

        store.unsubscribe(this.onStateChangeCallback);
    }

    onStateChange() {
        // brute force update entire UI on store change to keep demo app simple
        this.forceUpdate();
    }

The <Todos> component can now refresh the user interface each time the store’s state changes. <Todos> has an <h1> header with the number of incomplete items and is implemented with this change:

render() {
    const { todos } = this.props.store.shadow;
    const numIncomplete = todos.filter( t => !t.completed ).length;
    const remainingText = `${ numIncomplete } ${ pluralize("item", numIncomplete ) } remaining`;

    return <div className="todoContainer">
            <h1> F.lux Todos <small>{ remainingText }</small></h1>

            <AddTodo todos={ todos } />

            { this.renderTodos() }
        </div>
}

Here are the important sections broken down:

3. Implement <AddTodo> addTodo() function

The <AddTodo> component is now receiving a property containing the todos shadow array. This is used to implement the addTodo() function:

class AddTodo extends Component {
    addTodo() {
        const { todos } = this.props;
        const desc = this.todoInput.value;

        // Create a new Todo item
        const todo = {
            completed: false,
            desc,
            created: moment().toISOString()
        }

        // add the Todo item to the array
        todos.push(todo);

        // clear the input
        todoInput.value = "";
    }

Step 2 in this tutorial will move this logic to a property but for now the logic is inlined in the component for ease or readability and allow the focus to remain on interacting with the shadow state.

Here are the key points explained:

4. Implement <TodoItem> to display, edit, and delete todo items

A <TodoItem> displays three components on a single line:

Here is the full source for the component:

class TodoItem extends Component {
    removeTodo() {
        const { todo, todos } = this.props;
        const idx = todos.indexOf(todo);

        if (idx !== -1) {
            todos.remove(idx);
        }
    }

    handleToggleCompleted() {
        const { todo } = this.props;

        // toggle the completed flag
        todo.completed = !todo.completed;
    }

    render() {
        const { todo } = this.props;
        const { completed, desc } = todo;
        const descClasses = classnames("todoItem-desc", {
                "todoItem-descCompleted": completed
            });
        const completedClasses = classnames("todoItem-completed fa", {
                "fa-check-square-o todoItem-completedChecked": completed,
                "fa-square-o": !completed,
            });

        return <div className="todoItem">
                <i className={ completedClasses } onClick={ () => this.handleToggleCompleted() } />

                <input
                    type="text"
                    className={ descClasses }
                    onChange={ event => todo.desc = event.target.value }
                    defaultValue={ desc }
                />

                <i className="todoItem-delete fa fa-times" onClick={ () => this.removeTodo() }/>
            </div>
    }
}

Let’s start the discussion with the render() function:

Now let’s examine the simple handleToggleCompleted() function:

And finally, the removeTodo() function:

5. Iterate all store todo items and create a <TodoItem> for each one

And finally, let’s update <Todos> component to render a <Todo> component for each item in the todos array:

renderTodos() {
    const { todos } = this.props.store._;

    if (todos.length) {
        return todos
            .sortBy('completed')
            .map( t => <TodoItem key={ t.$().pid() } todo={ t } todos={ todos } /> );
    } else {
        return <p className="noItems">What do you want to do today?</p>
    }
}

This function is called in <Todos> render function.

Step 2: Properties