DEV Community

Anish Kumar
Anish Kumar

Posted on • Updated on

Redux is easier than you think

"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:

  1. It Saves time!
  2. It Reduces the moving parts of your program, thus leaving lesser chances of committing mistake
  3. 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:

Redux architecture

source:blog.apptension.com

Here are the key points :

  1. There's a plain javascript object, which contains the state for the whole application. (state)

  2. The state is immutable, meaning, it cannot be directly changed. For example, you can't do state.name="john"

  3. To make any changes in the state, you must dispatch an action (which is also a plain object).

  4. The reducer (a function) listens for any dispatched actions and accordingly mutates the state.

  5. 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:

  1. User should be able to add todos
  2. User should be able to mark incomplete todos as complete and vice-versa
  3. 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:

  1. An input, to let us add new todos
  2. A list showing all the todos
  3. 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)

Collapse
 
aroc725 profile image
Vance • Edited

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?