Front-end State Containers: Effective Selection

In today’s frontend ecosystem, there is an overflowing ocean of state containers and every developer is sailing with one or the other state container. State container is like an abstraction which helps in managing application state. If you are not aware of what a State container and State is then stop here, and read this blog before continuing.

In this tutorial, I’m going to show you how to create a simple “ToDo” app in React using some of the popular state container choices like, Hooks, Redux, Mobx, and setState. Let’s get started.

Quickly, see what we are going to build, I divided the ToDO application in three-part

1.    Add Item – this will add a new item in the to-do list
2.    Item List – this will compose a list of Items and display ToDo and Completed List
3.    Item – leaf level component which has

a.    Checkbox
b.    Item text
c.     Delete
4.    Filter component – take user input and filter out the list accordingly, we would be reusing it in multiple places so make it a Functional component

ToDo using React Hooks
1.            Let’s quickly define the Initial state of our application, I am creating a new file at root level with a name defaultState.js and adding dummy items in the list

import uniqueId from 'lodash/uniqueId';

const defaultState = [
    { value: 'Met my HR', id: uniqueId(), completed: false },
    { value: 'Submitted all BGV docs', id: uniqueId(), completed: false },
    { value: 'SEZ card', id: uniqueId(), completed: false },
    { value: 'Access to My Portal', id: uniqueId(), completed: false },
    { value: 'Downloaded Medical card', id: uniqueId(), completed: true },
    { value: 'Awareness about iTime', id: uniqueId(), completed: false },
    { value: 'Updated seat no. in ESS', id: uniqueId(), completed: false },
    { value: 'Updated Aadhar, UAN & PAN no. in ESS', id: uniqueId(), completed: false },
    { value: 'Vehichal declaration', id: uniqueId(), completed: false },
    { value: 'E-learnings on iSucess', id: uniqueId(), completed: true },
    { value: 'Flexi declaration', id: uniqueId(), completed: true },
  ];

  export default defaultState;

2.            Create a component folder under src/ which will host all our reusable/functional components


3.            Let’s create our first component which will encapsulate check box, label and delete button (icon). Create a new file with name Item.js under the component and paste below code. Please note: I am using React material for designing and lay-outing this application.

import React, {Component} from 'react';
import Checkbox from '@material-ui/core/Checkbox';
import DeleteIcon from '@material-ui/icons/Delete';
import IconButton from '@material-ui/core/IconButton';

function Item(props)
{
    const {item, onItemChange, onRemoveItem} = props;
   
        return (
                <div>
                    <label htmlFor={item.id}>
                        <Checkbox
                            id={item.id}
                            checked={item.completed}
                            onChange={onItemChange}/>
                        {item.value}
                    </label>
                    <IconButton title="Delete">
                     <DeleteIcon onClick={() => onRemoveItem(item)}/>
                    </IconButton>
                </div>
        );
}

export default Item;

in above code I have added material Checkbox, IconButton, and DeleteIcon to display single row in our list.  The core thing here is, we are passing a couple of items as props from its parent which we will create in a few minutes.  Here are the props we are passing
                                          i.    item – is a JS {object}, carry information regarding label, id, and completed - either the item is completed or not completed
                                         ii.    onItemChange – listener, whenever a user clicks on a checkbox, this listener will be called on. As this is a dumb component so we are asking its parent to control all the logic related to data manipulation
                                       iii.    onRemoveItem – listener, will get activated when user click on delete icon, again with same design philosophy we are delegating this task to parent component

4.            Let’s create our second component which will compose Item Component and render it as a list of items with a filter search field. Create a new file with name ItemList.js under the component and paste below code.

import React from 'react';
import Item from './Item';
import TextField from '@material-ui/core/TextField';

function ItemList(props)
{
    // Declare a new state variable
    const [searchedValue, setSearchedValue] = useState('');
    const items = props.list;
       
   
    const onChange =(event) =>{
         setSearchedValue(event.target.value);
    }

    return (
                <>
                    <TextField
                    label="Filter here"
                    onChange={onChange}/>

                    <div>
                    {
                        items
                        .filter(item => item.value.toLowerCase().includes(searchedValue.toLowerCase()))
                        .map(item => <Item
                                        key={item.id}
                                        item={item}
                                        onItemChange={props.onItemChange}
                                        onRemoveItem={props.onRemoveItem}
                                        />
                                )
                        }
                    </div>
                </>
            );

}

export default ItemList;

5.            In the code above, I have added one material text filed and composed Item component which we created in step 3. we are storing text field value in one of our state hook. As this is component internal state so we are storing it in component internal state system.

6.            Now quickly move to our smart parent development which will be responsible for composing ItemList component and then maintaining the state of application data.

import React,{useState} from 'react';
import ItemList from '../components/ItemList';
import AddItem from '../components/AddItem';
import defaultState from '../defaultState';
import Button from '@material-ui/core/Button';

function App(){
 
   // Declare a new state variable
  const [items, setItems] = useState(defaultState);

  const todoList = items.filter(item => !item.completed);
  const completedList = items.filter(item => item.completed);

  const onAddItem =(item) =>{
    const toDoItems = [item,...items];

    setItems(toDoItems)
  }

  const onRemoveItem =(itemToRemove) =>{
    const toDoItems = items.filter(item => item.id !== itemToRemove.id);
    setItems(toDoItems);
     
  }

  const onToggle = (toggleItem) =>{
    const toDoItems = items.map(item =>{
        if(toggleItem.target.id !== item.id) return item;
        return {...item, completed:!item.completed}
      })

      setItems(toDoItems);
  }

 

  const markAllCompleted =()=> {
    const toDoItems = items.map(item => {
      return {...item, completed:true};
    } )
   
    setItems(toDoItems);
  }

  return (
    <>
      <AddItem onSubmit={onAddItem} />
      <ItemList
            title="ToDo List"
            list={todoList}
            onItemChange={onToggle}
            onRemoveItem = {onRemoveItem} />

      <ItemList
            title="Completed List"
            list={completedList}
            onItemChange={onToggle}
            onRemoveItem = {onRemoveItem}/>
     
      <Button
          variant="contained"
          color="primary"
          onClick={markAllCompleted}
          type="submit"
          style={{margin: '25px 0 0 30%'}} >Mark All Completed
      </Button>
    </>
  );
}

export default App;

7.            Let me do a quick comparison between the React state and Hook-based state system.
                                          i.    Programming component without “this”: you would not find ‘this’ here, and still able to preserve the state in the component. Were-in if you write a class-based state component you would have to use “this” a lot. I consider this as a natural progression towards functional programming.

8.            Looking at the above benefits I can see that Hooks are the clear winner over traditional react state system. Now quickly see how Redux state system can be injected here and what all benefits we can reap out of it.

ToDo using Redux

9.            Redux, in short, it’s a Predictable state container for building JS apps.  Few things to bear in mind
                                          i.    Redux is NOT ONLY for React, its for any JS application, that’s why its framework agnostic
                                         ii.    React-Redux is an opinionated binding library which is specific to binding React component system with Redux state system.
                                       iii.    As it’s a third party library, you need to add a couple of actors(files) in to react your application to make it fully functional, I am not adding information on every actor here but if you want there is good the documentation you can find here Store, Action, and Reducer etc.
Image result for redux design

10.         To implement Redux as state management system we need to create multiple files adhere to its core principles (immutability, pure functions). You can find the full code here git repo. Copy the whole redux folder under your src/.


11.         Lets quickly talk about App.js, one of the mainstream file which is holding the binding between React and Redux.

import React,{Component} from 'react';
import ItemList from '../components/ItemList';
import { connect } from 'react-redux';
import {addItem,removeItem, toggleItem, markAllItemsCompleted } from './Actions';
import AddItem from '../components/AddItem';
import Button from '@material-ui/core/Button';

class App extends Component{

  onAddItem =(item) =>{
    this.props.addItem(item);
  }

  onRemoveItem =(item) =>{
    console.log('on remove item in  APP ' + item.id);
    this.props.removeItem(item);
  }

  onToggle = (toggleItem) =>{
    this.props.toggleItem({id:toggleItem.target.id});
  }

  markAllCompleted =()=> {
    this.props.markAllCompleted();
  }

  render()
  {
      const todoList = this.props.items.filter(item => !item.completed);
      const completedList = this.props.items.filter(item => item.completed);

      return (
             <>
                <AddItem onSubmit={this.onAddItem} />
          
                <ItemList
                          title="ToDo List"
                          list={todoList}
                          onItemChange={this.onToggle}
                          onRemoveItem = {this.onRemoveItem}
                        />

                <ItemList
                          title="Completed List"
                          list={completedList}
                          onItemChange={this.onToggle}
                          onRemoveItem = {this.onRemoveItem}
                        />
               
                <Button
                  variant="contained"
                  color="primary"
                  onClick={this.markAllCompleted}
                  type="submit"
                  style={{margin: '25px 0 0 30%'}} >Mark All Completed
                </Button>
               </>);
  }
}

const mapStateToProps = (state) => {
  return { items: state.items };
};

const mapDispatchToProps = dispatch => {
  return {
    addItem: (item) => {
      dispatch(addItem(item))
    },
    removeItem: (item) => {
      dispatch(removeItem(item))
    },
    toggleItem: (item) => {
      dispatch(toggleItem(item))
    },
    markAllCompleted: () => {
      dispatch(markAllItemsCompleted())
    }
  };
};

export default connect(mapStateToProps,mapDispatchToProps)(App);

12.         Key highlight to talk about here is you need to write connect(mapStateToProps,mapDispatchToProps)(App); to make a connection

13.         Again, let me do a quick comparison between our last winner Hook and Redux
                                          i.    Redux is all about design principles (Immutation, Single source of truth). When I was writing my redux state container, I was feeling like I have to write a lot to create a small ToDo application. I had written almost 4 new files to preserve Application state in the redux predictable container. Looks a LOT !
                                         ii.    On the other hand, if I evaluate Redux approach from a Manageability perspective, it brings a LOT! Honestly speaking if you are working in multi-located team or having a good team of developers working parallelly on different features, Redux brings them all under strict development protocol. It’s worth to mention that a manageable code base can scale with good velocity. So Scalable as well! interesting
                                       iii.    Final outcome: Good for bringing Manageability and Scalability in your application but for an application like ToDo where you have very limited features to implement Redux could be overengineering stuff.

ToDo using Mobx

14.         Mobx, in short, it’s a Reactive state container for building JS apps.  Few things about Mobx
                                          i.    Its all about observable — observer flow. You declare some data to be observable and when that data changes all the observers that are using that data will be notified.
                                         ii.    Mobx is not only for React , it’s for any JS application, that’s why it’s also framework agnostic
                                       iii.    Mobx-React is an opiniated binding library which is specific to binding React component system with Mobx state system.
Image result for Mobx design

15.         To implement Mobx as state management system we need to create multiple files to adhere its core principles (mutability). You can find the full code from here git repo. Copy the whole mobx folder under your src/.


16.         Lets quickly talk about App.js, one of the main stream files which is holding the binding between React and Mobx. Let me quickly talk about here

import React, { Component } from 'react';
import { observer } from 'mobx-react';
import ItemList from './components/ItemList';
import AddItem from './components/AddItem';
import ItemStore from './models/ItemStore';
import defaultState from '../defaultState';
import Button from '@material-ui/core/Button';

const App = observer(class App extends Component
{
    itemStore;

    constructor(props)
    {
        super(props);
        this.itemStore = ItemStore.fromJS(defaultState);
    }

    addItem =(item) =>{
        this.itemStore.addItem(item.value);
    }

    markAllCompleted =()=> {
        this.itemStore.toggleAll(true);
    }
     
    render()
    {
        const todoList = this.itemStore.items.filter(item => !item.completed);
        const completedList = this.itemStore.items.filter(item => item.completed);

        return (
                   <>    
                      <AddItem onSubmit={this.addItem} />
                     
                   <ItemList
                            title="ToDo List"
                            list={todoList}/>
               
                   <ItemList
                          title="Completed List"
                          list={completedList}/>
                     
                      <Button
                           variant="contained"
                           color="primary"
                           onClick={this.markAllCompleted}
                           type="submit"
                           style={{margin: '25px 0 0 30%'}} >Mark All Completed
                      </Button>
                </>
        )
    }
});

export default App

17.         Key points to watch here are
                                          i.    As I am not using TS, I can't use the @observer decorator which has been implemented as part of ES7 specification, I have to use some Observer utility to register our App component as Observer.
                                         ii.    You also need to define a Model and a store and decorate them with observable type.

18.         Again, let me do a quick comparison between Hooks, React State, Redux and Mobx, I have not written on React state here as it was the de facto approach.
                                          i.    Mobx is about Reactive programming, you need to define observer and observable and rest will be taken care by Magic box. Less Boilerplate code, at the same time harder to debug
                                         ii.    You need to define the state in some data models which is very close to the object-oriented paradigm. Feel at Home if you are an advocate of OO
                                       iii.    Final outcome: Good for applications where you have co-located mid-size team of developers working on building the mid-size application.


Fitment, where should we use which state management approach?
A very burning question nowadays, I see lots of developers start their application development by looking at community trends, which is right as well, but they tend to forget about sizing their own application domain problem. In simple English, if I have to develop a ToDo application, rather than going with Redux or Mobx (as they are trending a lot), I can fit a React Hooks approach because ToDO application is a low complexity app. I don’t want to overengineer it by infusing Redux or Mobx into it. Some more use cases based on the requirement
1.    Small size application with no server-side rendering
A small size application is the one where one or two developers are working continuously to build 3-4 features, typically not more than of 3-4 months of work, with almost no chance of adding more features in the future. We can easily go with Hooks or React state here

2.     A mid-size project with a deep hierarchy in the state
A mid-size application is like 3 or 4 developers working to build 10-15 features, typically not more than of 6-8 months of work, the possibility of adding new features in future are moderate. Select Mobx as a tool for state management.

3.    Big project, Global state, state reusability is high – Redux
A Large size application, where a team of more than 7-8 developers is required to build a good number of features, we can choose to go with Redux solution.


Comments

Popular posts from this blog

MicroFrontends

Frontend State Management

Push over HTTP