Step 4: f.lux-react

Overview

This step will will demonstrate using the f.lux-react module streamline access to the f.lux Store and its shadow state properties.

Goals

  1. <Provider>
  2. <AddTodo>
  3. Simplify <Todos> Top-Level Component
  4. <Header>
  5. <TodosList>
  6. <Toolbar>

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

Technical background

f.lux-react

The f.lux-react module was inspired by the redux-react module for providing React bindings. Using f.lux-react removes the need for you to hand code:

  1. Registering for Store change notificiations
  2. Passing shadow properties from the top-level component through the entire hierarchy

f.lux-react contains components and functions to:

This tutorial step will utilize the following f.lux-module exports:

1. <Provider>

Using the <Provider> component to place your Store instance on the React context is straight-forward. Here is the pertitent code in main.js:

import { Provider } from "f.lux-react";

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

Essentially, we just wrapped the application’s top-level component, <Todos>, in a <Provider>.

2. <AddTodo>

The <AddTodo> component expects a todos prop that contains a reference to the todos shadow state property. Previously, this was passed by the <Todos> component. We can use storeContainer() instead:

class AddTodo extends Component { ... }

export default storeContainer( shadow => ({ todos: shadow.todos }) )(AddTodo);

No <AddTodo> changes are required since the f.lux connected component is exported and used in <Todos> as shown in the next section.

3. Simplify <Todos> Top-Level Component

Refactoring the other components allows for <Todos> to be greatly simplified. Previous versions contains the code to register/unregister from the f.lux store. In fact, we are also going to extract the header, item list, and toolbar into separate components yeilding a simple functional component:

import React from "react";

import AddTodo from "./AddTodo.react";
import Header from "./Header.react";
import TodoList from "./TodoList.react";
import Toolbar from "./Toolbar.react";


export default function Todos(props, context) {
    return <div className="todoContainer">
            <Header />

            <AddTodo />
            <TodoList />

            <Toolbar />
        </div>
}

Notice how the top-level <Todos> component no longer references the store prop. The following sections will discuss the new components: <Header>, <TodoList>, and <Toolbar>.

4. <Header>

The application header is an <h1> tag that shows the number of incomplete items. The code is extracted from the <Todos> render() function and placed in the <Header> functional component:

import pluralize from "pluralize";
import React from "react";

import { storeContainer } from "f.lux-react";


function Header(props, context) {
    const { todos } = props;
    const remainingText = `${ todos.incompleteSize } ${ pluralize("item", todos.incompleteSize ) } remaining`;

    return <h1>
            F.lux Todos <small>{ remainingText }</small>
        </h1>
}


export default storeContainer( shadow => ({ todos: shadow.todos }) )(Header);

5. <TodosList>

The previous steps rendered the todo items from the <Todo> renderTodos() method. This becomes another simple functional component:

import React from "react";

import { storeContainer } from "f.lux-react";

import TodoItem from "./TodoItem.react";


function TodoList(props, context) {
    const { todos, ui } = props;
    const visibleTodos = ui.visibleTodos();

    if (todos.length === 0) {
        return <p className="noItems">What do you want to do today?</p>
    } else if (visibleTodos.length === 0) {
        return <p className="noItems">No items are { ui.filter }</p>
    }

    return <div>
        {
            visibleTodos.map( t => <TodoItem key={ t.$().pid() } todo={ t } todos={ todos } /> )
        }
        </div>
}


export default storeContainer( shadow => shadow )(TodoList);

The one wrinkle here is the mapStateToProps() function:

shadow => shadow

storeContainer() will merge the object with the explicitly set component props with the result being the shadow.todos and shadow.ui properties will be passed as todos and ui props. You would not usually want all of the shadow root state child properties passed as props but things are pretty simple here. The component then obtains references to todos and ui:

const { todos, ui } = props;

6. <Toolbar>

You do not wrap every component using storeContainer() because that introduces unnecessary overhead. The goal is to strike a balance between manually passing the store and/or shadow state through the entire hieararchy and wrapping every component. Toolbar demonstrats getting the ui shadow state property using storeContainer() and then passing it to the <FilterSelector> and <SortSelector>.

import React from "react";

import { storeContainer } from "f.lux-react";

import FilterSelector from "./FilterSelector.react";
import SortSelector from "./SortSelector.react";


function Toolbar(props, context) {
    const { ui } = props;

    return <div className="tools">
            <FilterSelector ui={ ui } />
            <SortSelector ui={ ui } />
        </div>
}


export default storeContainer( shadow => ({ ui: shadow.ui }) )(Toolbar);

No changes are required for <FilterSelector> and <SortSelector> to continue functioning as in the previous step.

Final Thoughts

This tutorial step covered using the f.lux-react module to simplify the application’s React components.

Important concepts include:

Next Step

Step 5: Time travel debugger