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.
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
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.
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.
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
Post a Comment