"The number one complaint I see about Redux is that there's "too much boilerplate". I also frequently see complaints that there's too much to learn, too many other addons that are needed to do anything useful, and too many aspects where Redux doesn't have any opinion and therefore doesn't offer any kind of built-in guidance..."
This comment precisely describes how overwhelming it can be for a beginner to get started with the core concepts of redux. The above text has been borrowed from an active issue on official redux repo (source : https://github.com/reactjs/redux/issues/2295). The kind of response this issue has received from the community clearly shows that the issue is real. And it's not something that only beginners face, in fact, any efficient developer wouldn't be a fan of repeating the same piece of code over and again, specially when it can be abstracted away.
Abstraction of repetitive boilerplate/functionality offers some great perks, such as:
- It Saves time!
- It Reduces the moving parts of your program, thus leaving lesser chances of committing mistake
- It makes your code cleaner and thus easier to maintain
More options = more moving parts = higher chances of mistake
Let's use (redux - the noise)
I will be using the classic todo-list example to illustrate how simple redux can be. But before that, here's a diagram, illustrating the core philosophy of redux in simplest terms:
source:blog.apptension.com
Here are the key points :
There's a plain javascript object, which contains the state for the whole application. (state)
The state is immutable, meaning, it cannot be directly changed. For example, you can't do
state.name="john"
To make any changes in the state, you must
dispatch
anaction
(which is also a plain object).The
reducer
(a function) listens for any dispatched actions and accordinglymutates
the state.Finally, the state gets updated and the view is rendered again to show the updated state.
Don't worry if that's confusing, the fun part begins now :
We have 3 simple objectives for our todo app:
- User should be able to add todos
- User should be able to mark incomplete todos as complete and vice-versa
- User should be able to delete todos
Let's start with a fresh react-application:
create-react-app todo-redux
Also let's pull in redux-box
to set up redux, redux-saga, dev tools and much more in a trice:
npm install --save redux-box
Great, we've got the essentials. Let's set up our redux-store quickly by creating src/store
folder. This is where we would be programming anything related to the store. Redux box emphasizes on modular store, i.e split your store into multiple modules to manage stuffs easily.
For our todo-app we will have just one module, for simplicity. Let's call it todo.js
. The module would specify it's initial state, actions and mutations, like so:
const state = {
items: [],
}
const actions = {
}
const mutations ={
}
export const module = {
name : 'todos',
state,
actions,
mutations
}
That's the bare-bones of our module. Let's register it with the global store :
src/store/index.js
import {createStore} from 'redux-box';
import {module as todoModule} from './todo'
export default createStore([
todoModule
])
There we go! We have set up our redux store with all the needed bells and whistles in just a few lines of code. (You could have set up redux-saga as well, but since our todo app won't require that, I'm skipping the snippet showing how sagas can be used in a module. You may want to refer to the repo if you are curious enough. )
Final step in the setup is to wrap our root component around Provider
, so that the app can recognise our store:
src/App.js
import {Provider} from 'react-redux';
import store from './store'
import TodoMain from './components/TodoMain'
class App extends Component {
render() {
return (
<Provider store={store} >
<div className="App">
<TodoMain></TodoMain>
</div>
</Provider>
);
}
}
export default App;
Here components/TodoMain.js
is our main component where we will be putting our UI and integrating it with our todo module
.
In TodoMain.js
, we would have:
- An input, to let us add new todos
- A list showing all the todos
- A delete-icon alongside each list item, allowing us to delete the todo
Here's how our final TodoMain.js
looks like:
import React, { Component } from 'react'
export default class TodoMain extends Component {
constructor(){
super();
}
render() {
return (
<div className="main">
<h1>Todos Manager</h1>
<input type="text"/>
<ol className="todos">
// list items go here
</ol>
</div>
)
}
}
Writing the logic to add, delete and toggle todos
We would need three mutations
, for adding, deleting and toggling todos. And for each mutation, we will be creating an action, so that our components can call those mutations (a component can directly access state
and actions
of any module, but nothing else). Thus our todo module
looks like this:
const state = {
items: [],
active_todo :
}
const actions = {
addTodo : (todo) => ({
type: 'ADD_TODO' ,
todo }) ,
toggleTodoStatus : (todo) => ({
type : 'TOGGLE_TODO',
todo}),
deleteTodo : (todo) => ({
type : 'DELETE_TODO',
todo
})
}
const mutations ={
ADD_TODO : (state, action) => state.items.push(action.todo),
TOGGLE_TODO : (state, {todo}) => {
state.items = state.items.map(item => {
if(item.id== todo.id)
item.completed = !item.completed
return item
});
},
DELETE_TODO : (state, {todo}) => {
let index = state.items.findIndex(item => item.id==todo.id);
state.items.splice(index, 1);
}
}
export const module = {
name : 'todos',
state,
actions,
mutations
}
Finally, let the component interact with module logic
To connect our store with component, we use connectStore
decorator from redux-box
. The decorator then attaches the module to the prop of the component:
import React, { Component } from 'react'
import {module as todoModule} from '../store/todos';
import {connectStore} from 'redux-box';
import cn from 'classnames';
@connectStore({
todos : todoModule
})
class TodoMain extends Component {
constructor(){
super();
this.state ={
todo : ''
}
}
addTodo = (item) => {
if(e.keyCode==13)
todos.addTodo({
id : Math.random(),
text: this.state.todo,
completed: false
})
}
render() {
const {todos} = this.props
return (
<div className="main">
<h1>Todos Manager</h1>
<input type="text" value={this.state.todo}
onChange={(e)=>{
this.setState({ todo : e.target.value})
}}
onKeyUp = {(e)=> this.addTodo(e.target.value)}
/>
<ol className="todos">
{
todos.items.map((item,i) => {
return <li
className={cn({'completed': item.completed})}
onClick={()=> todos.toggleTodoStatus(item) }
key={i}>
{item.text}
<i class="fa fa-trash"
onClick= { (item) => todos.deleteTodo(item) }
></i>
</li>
})
}
</ol>
</div>
)
}
}
export default TodoMain
That's it...
You see! Redux is easy. It's purpose is to make your life easier, so keep it simple :)
And yes, Feel free to star redux-box at GitHub, if you think it truly helps!
Top comments (1)
I can't get 'TodoMain.js' to compile, I'm getting the following error message about the 'addTodo' method:
Failed to compile.
./src/components/TodoMain.js
Line 18:7: 'e' is not defined no-undef
Line 19:4: 'todos' is not defined no-undef
Any ideas as to how to resolve these errors?