October 06, 2019

Using React Hooks in Redux Application

Most developers don’t like redux for so many reasons , but the introduction of react hooks in react 16.8 will some how reduce the pain of using redux .

Among the biggest pain is the wiring that needs to happen when you want to connect a component to the redux store , all these Higher-Order Component usage , the connect , mapStateToProps and mapDispatchToProps make it quite cumbersome .

Let’s begin , we are going to create a ToDo Application in which you can add a todo , toggle the state of a task as well as delete a todo .

The Final code for this tutorial can be found here https://github.com/robertrutenge/todo-redux-hooks

Let’s Start . We are going to use create-react-app to bootstrap our new application , let’s call it todos

create-react-app todos

Let’s cd to our application and start it by running

npm start

We are going to use bootstrap to give some styling to our components , so let’s install it first ;-

npm install bootstrap

Let’s start with the easy stuffs first , creating our components . On our todo application we are going to have 2 components , one for inputing new todo item and another one to display the list of todos . The list of todos will have an option of marking wether a task is complete or not and another option to delete a todo .

Delete everything on App.js file and add the two components . Don’t forget to add bootstrap.css file in the index.js file or app.js file .

import React from "react";
import "bootstrap/dist/css/bootstrap.css";
import "./App.css";

function App() {
  return (
    <div className="App">
      <ToDoInput />
      <ToDoList />
    </div>
  );
}

export default App;

Let’s go ahead and create the two components . Under the scr folder , create a new components folder with two files ToDoInput.js and ToDoList.js .

import React from "react";

const ToDoInput = () => {
  return (
    <form>
      <div className="row mt-3">
        <div className="form-group col-sm-8">
          <input
            type="text"
            placeholder="Add a todo"
            name="todo"
            className="form-control"
          />
        </div>
        <div className="col-sm-4">
          <button type="submit" className="btn btn-primary btn-md">
            Add Todo
          </button>
        </div>
      </div>
    </form>
  );
};

export default ToDoInput;
import React from "react";

const ToDoList = () => {
  return (
    <div>
      <p className="alert alert-info">No todos at the moment</p>
    </div>
  );
};

export default ToDoList;

The code above , should be straightforward , and it should give something like below : Redux Hooks form

Since , we will be using redux as our state management library , lets go ahead and install redux and react-redux .

npm install redux react-redux

Once Installed , Let’s create our redux store , go on and create store folder inside it , index.js file with the code below :

import { createStore } from "redux";
import rootReducer from "./reducers";

export const store = createStore(
  rootReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

Note: the second parameter in createStore function is for redux dev tool , so go on and install the tool

The createStore() function from redux will need a reducer , so let’s go on and create a reducer.js file inside the project folder with the below code :

const initialState = {
  todos: [],
};

function appReducer(state = initialState, action) {
  switch (action.type) {
    case "ADD_TODO":
      return { ...state, todos: [...state.todos, action.payload] };
    case "TOGGLE_TODO":
      return {
        ...state,
        todos: state.todos.map((todo) =>
          todo.id === action.payload
            ? { ...todo, complete: !todo.complete }
            : todo
        ),
      };
    case "DELETE_TODO":
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload),
      };
    default:
      return state;
  }
}

export default appReducer;

As we all know in redux , the only way to communicate with our store is through action , so let’s go on and add our action creators . Inside project directory create action-creators.js file .

export const addTodoAction = todo => ({
  type: "ADD_TODO",
  payload: todo
});

export const toggleTodoAction = todoId => ({
  type: "TOGGLE_TODO",
  payload: todoId
});

export const deleteTodoAction = todoId => ({
  type: "DELETE_TODO",
  payload: todoId
});

Now we should be good to go with the redux part , the only missing part is connection the store to our application . We will do that by using a Provider from react-redux . Let’s change our App.js file by wrapping it with our provider and supplying the store we just created .

import React from "react";
import { Provider } from "react-redux";
import ToDoInput from "../src/components/ToDoInput";
import ToDoList from "../src/components/ToDoList";
import { store } from "./store";
import "bootstrap/dist/css/bootstrap.css";
import "./App.css";

function App() {
  return (
    <Provider store={store}>
      <div className="container App">
        <ToDoInput />
        <ToDoList />
      </div>
    </Provider>
  );
}

export default App;

Finally , we manage to connect the store and everything should work as expected . If you have Redux Dev Tools installed , and inspect our application , you should be able to see our store with a todos empty array as below :

Redux tool inspector

Now , on to the final part . Using React Hooks to our redux application . Hooks allow access of state and lifecycle methods in stateless / functional components . Since react-redux v7.1.0 , some hooks APIs have been introduced as an alternative to HOC connect() function that’s normally used when you want to connect to the store .

The most used react redux hooks APIs are useSelector() that allows the access of our redux store data and useDispatch() that returns a reference to the dispatch function .

We will use the two APIs for adding , toggling and deleting our todos .

Let’s start with adding todos , let’s change our . Before that make sure you install uuid which we will use to generate a random id .

npm install uuid

import React from "react";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { addTodoAction } from "../store/actions";
import uuid from "uuid/v4";

const ToDoInput = () => {
  const [todo, setToDo] = useState("");
  const dispatch = useDispatch();
  const addToDo = todo => dispatch(addTodoAction(todo));

  const handleChange = event => {
    setToDo(event.target.value);
  };

  const submitForm = event => {
    event.preventDefault();
    const newTodo = {
      id: uuid(),
      name: todo,
      complete: false
    };
    addToDo(newTodo);
    setToDo("");
  };
  return (
    <form onSubmit={submitForm} method="POST">
      <div className="row mt-3">
        <div className="form-group col-sm-8">
          <input
            type="text"
            placeholder="Add a todo"
            name="todo"
            className="form-control"
            value={todo}
            onChange={handleChange}
          />
        </div>
        <div className="col-sm-4">
          <button type="submit" className="btn btn-primary btn-md">
            Add Todo
          </button>
        </div>
      </div>
    </form>
  );
};

export default ToDoInput;

So , explaining what happened above . We first use useState hook from react for storing our state which is todo . setToDo is the same as this.setState() method in stateful component and it does just That .

Note that , useState() passed a parameter that defines initial state , and in this new hook implementation the state does not necessarily needs to be an object compared to the stateful component implementation . In our case its an empty string .

So , on the input we have added the value property that will hold the current state , and the onChange method that will use setTodo() to change the value of our state .

The onSubmit method calls the submit function that will in turn calls addToDo method that will use UseDispatch() hook from react-redux to dispatch an action in our case addToDoAction() .

If all goes well , you should be able to add your todo with no error and when inspect in our redux store you should see the newly added todos .

Displaying the todos

Now let’s finalize our application , by displaying the todos and adding functionality to toggle the state of the todo task as well as delete the todo .

Modify component as below :

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { toggleTodoAction, deleteTodoAction } from "../store/actions";
const TodoList = () => {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  const toggleTodo = id => dispatch(toggleTodoAction(id));
  const deleteTodo = id => dispatch(deleteTodoAction(id));

  return (
    <div>
      {todos && todos.length === 0 && (
        <p className="alert alert-info">No Todos at the moment</p>
      )}
      {todos &&
        todos.map(todo => (
          <div key={todo.id} className="row mb-1">
            <div className="col-sm-2">
              <input
                type="checkbox"
                checked={todo.complete}
                onChange={toggleTodo.bind(null, todo.id)}
              />
            </div>
            <div className="col-sm-8">
              <span>{todo.name}</span>
            </div>
            <div className="col-sm-2">
              <button
                className="btn btn-danger"
                onClick={deleteTodo.bind(null, todo.id)}
              >
                X
              </button>
            </div>
          </div>
        ))}
    </div>
  );
};
export default TodoList;

From the above implementation , useSelector hook was used to get the current state in our store for our todos . The useSelector is equivalent to mapStateToProps argument in the connect function .

The toggle and delete actions should be as the addToDo we saw earlier .

Once you start to use the hooks , you will start to realize how efficient and effective they are . Remember the hooks did not come to replace anything , so no need to rewrite your projects to use them , you can just introduce them in new development as they don’t have any breaking changes .

Buy Me A Coffee

Robert Rutenge
Front-end Developer | ReactJS enthusiast | Problem Solver . Find me on twitter and Github