Learnitweb

Redux store example

1. Introduction

In this tutorial, we’ll create a simple application and will use Redux to update the state.

2. Design the Redux Store

The application we are going to create is an application to manage tasks. The state of the application maintains the tasks. We maintain an array to store our tasks. Our array of tasks looks like the following:

[
    {
        id: 1,
        task: "Design Redux store",
        isComplete: false
    },
    {
        id: 2,
        task: "Create Redux store",
        isComplete: false
    }
]

In the actual production application, this tasks array will be a part of Redux Store. A Redux Store can have many more items. For example:

{
    tasks: [
        {
            id: 1,
            task: "Design Redux store",
            isComplete: false
        },
        {
            id: 2,
            task: "Create Redux store",
            isComplete: false
        }
    ],

    articles: [{...}, {...}, {...}],
    students: [{...}, {...}, {...}]
}

So in a real world application, you’ll have to write 3 reducers – one for tasks, second for articles and third for students data. However, in this application we’ll only maintain single type of data in the Redux Store, i.e. tasks.

2. Listing all actions

In our application we have 3 actions:

  • ADD_TASK
  • REMOVE_TASK
  • TASK_COMPLETED

Every action is represented by an object. This object will have at least one property name or type of action. The second key in the object usually is the payload. Payload contains information to be passed. Here is the example of our ADD_TASK action:

{
    type: "ADD_TASK",
    payload: {
        task: "Task 1"
    }
}

Any string or can be used for the value for type like taskAdded. Some developers use integers instead of String. But String is more descriptive.

Every change is described as an action. This lets us have a clear understanding of everything done in the app. If something changed, we know why it changed.

The value of type is a String. It is recommended to keep such strings at one place. So we can create actionType.js to keep such values for action type.

actionTpe.js

export const ADD_TASK = "ADD_TASK";
export const REMOVE_TASK = "REMOVE_TASK";
export const TASK_COMPLETED = "TASK_COMPLETED";

Now let us create action.js.

action.js

import * as actionTypes from "./actionTypes";

export const addTask = (task) => {
  return { type: actionTypes.ADD_TASK, payload: { task: task } };
};

export const removeTask = (id) => {
  return { type: actionTypes.REMOVE_TASK, payload: { id: id } };
};

export const completedTask = (id) => {
  return { type: actionTypes.TASK_COMPLETED, payload: { id: id } };
};

The action.js defines the different actions. The action is passed as as argument to the reducer.

The payload for the addTask is the task itself. For the removeTask the payload contains the task Id to be removed. For completedTask, the payload contains the Id of the task to be marked as complete.

3. Creating reducer

Reducer ties the state and actions together. Reducer is just a function that takes state and action as arguments, and returns the next state of the app. In the reducer we add small functions which manage parts of the state.

import * as actionTypes from "./actionTypes";
let id = 0;

export default function reducer(state = [], action) {
  switch (action.type) {
    case actionTypes.ADD_TASK:
      return [
        ...state,
        {
          id: ++id,
          task: action.payload.task,
          completed: false,
        },
      ];
    case actionTypes.REMOVE_TASK:
      return state.filter((task) => task.id != action.payload.id);
    case actionTypes.TASK_COMPLETED:
      return state.map((task) =>
        task.id === action.payload.id
          ? {
              ...task,
              completed: true,
            }
          : task
      );
    default:
      return state;
  }
}

There are 3 actions in the Reducer representing each type of action – ADD_TASK, REMOVE_TASK and TASK_COMPLETED.

If the action type is ADD_TASK, the reducer adds the task to the list of tasks.

If the action type is REMOVE_TASK, the reducer filters the task with the given id to be removed and return the tasks array.

If the action type is TASK_COMPLETED, the reducer updates the completed key of the task to true.

4. Create Store

Here is the code of store.js

import { legacy_createStore as createStore } from "redux";
import reducer from "./reducer";

const store = createStore(reducer);

export default store;

Creating a store is simple, pass the reducer to the createStore method.

5. Test our application

To test our application, here is code of index.js

import store from "./store";

console.log(store);

If you observe the console, our store is printed.

Now do the following code to the index.js to add the task.

import store from "./store";
import { addTask } from "./action";

console.log(store);
store.dispatch(addTask("Task 1"));

console.log(store.getState());

And you can check the console to see the following output.

Similarly, you can add code for actions REMOVE_TASK, TASK_COMPLETED.

// to remove task
store.dispatch(removeTask(1);

// to complete a task
store.dispatch(completedTask(1));

6. Conclusion

In this short tutorial, we created a simple application using Redux. This example uses the legacy way of writing Redux code. In the upcoming tutorials, we’ll see the modern and recommended approach of writing Redux code, i.e. Redux Toolkit.