Skip to content

Getting Todo Items from Backend

In order to be able to communicate with backend we will use fetch library that is built in any modern browser. You can find more here https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch.

First, we will make a new action getTodos that will call our GET endpoint on backend, get the data and dispatch action to update our store.

export function addItem(title: string) {
    return {
        type: 'ADD_ITEM',
        title
    }
}

export function removeItem(item_id: number) {
    return {
        type: 'REMOVE_ITEM',
        item_id
    }
}

export function markItemAsDone(item_id: number) {
    return {
        type: 'MARK_ITEM_DONE',
        item_id
    }
}

export function getTodos() {
    return async (dispatch: any) => {
        try {
            const todoList: any = await fetch('http://localhost:3001/todos', {
                method: 'GET'
            });

            const body = await todoList.json();

            if (todoList.status !== 200) {
                return dispatch({ type: 'GET_TODOS_ERROR', message: body.message });
            }

            return dispatch({ type: 'GET_TODOS_SUCCESS', items: body.data });
        } catch (error) {
            return dispatch({ type: 'GET_TODOS_ERROR', message: 'Something went wrong.' });
        }
    };
}

You will notice that this action implementation differs from ones we previousely created since we are using redux-thunk middleware.

We basically here request list of todo items from backend and we check if response is successful. If yes, we will dispatch action with type GET_TODOS_SUCCESS and pass list of todo items from backend, if not we will dispatch action GET_TODOS_ERROR with error message from backend to reducer.

Since we already added "Todo 1" item while we were testing endpoints, body value should look like

{
    "data": [
        {
            "_id": "61f358e91af28f1b4ad24bdd",
            "title": "Todo 1",
            "is_done": true,
            "__v": 0
        }
    ]
}

Also we make sure that if anything else is wrong (like typos), we dispatch GET_TODOS_ERROR with default "Something went wrong." message in catch block.


Now we have 2 new action types to handle in our reducer GET_TODOS_SUCCESS and GET_TODOS_ERROR. We will also define error parameter so we can store error message there and show to user if something is wrong.

export interface Todo {
    id: number;
    title: string;
    is_done: boolean;
}

interface TodoReducerInterface {
    todo_list: Todo[],
    error: '',
}

const INITIAL_STATE: TodoReducerInterface = {
    todo_list: [],
    error: '',
}

const todoReducer = (state = INITIAL_STATE, action: any) => {
    switch (action.type) {
        case "ADD_ITEM":
            return {
                ...state,
                todo_list: [
                    ...state.todo_list,
                    {
                        id: Math.floor(Math.random() * 1000000) + 1,
                        title: action.title,
                        is_done: false
                    }
                ]
            }
        case "REMOVE_ITEM":
            const removeItemIndex = state.todo_list.findIndex((item: any) => item.id === action.item_id);

            if (removeItemIndex < 0) {
                return state;
            }

            return {
                ...state,
                todo_list: [
                    ...state.todo_list.slice(0, removeItemIndex),
                    ...state.todo_list.slice(removeItemIndex + 1),
                ]
            }
        case "MARK_ITEM_DONE":
            const doneItemIndex: number = state.todo_list.findIndex((item: any) => item.id === action.item_id);

            if (doneItemIndex < 0) {
                return state;
            }

            return {
                ...state,
                todo_list: [
                    ...state.todo_list.slice(0, doneItemIndex),
                    {
                        ...state.todo_list[doneItemIndex],
                        is_done: !state.todo_list[doneItemIndex].is_done
                    },
                    ...state.todo_list.slice(doneItemIndex + 1),
                ]
            }
        case "GET_TODOS_SUCCESS":
            return {
                ...state,
                todo_list: action.items,
                error: '',
            }
        case "GET_TODOS_ERROR":
            return {
                ...state,
                todo_list: [],
                error: action.message
            }
        default:
            return state;
    }
};

export default todoReducer;

Awesome. But if you refresh the page, you will probably not see any change there.

It is because we never dispatched getTodos(). We want to load our todo list when our application is loaded. We can to it inside TodoList container.

import { useDispatch, useSelector } from "react-redux";
import ListHeader from "../components/ListHeader";
import List from "../components/List";
import { addItem, getTodos, markItemAsDone, removeItem } from "../store/actions/todo";
import { useEffect, useState } from "react";
import { RootState } from "../store/reducers";

function TodoList() {
    const dispatch = useDispatch();
    const [title, setTitle] = useState<string>("");

    const todo: any = useSelector((state: RootState) => state.todo);

    const addItemToList = () => {
        if (!title) return;

        dispatch(addItem(title));
        setTitle('');
    };

    const removeItemFromList = (item_id: number) => {
        dispatch(removeItem(item_id));
    };

    const toggleItemDone = (item_id: number) => {
        dispatch(markItemAsDone(item_id));
    };

    useEffect(() => {
        dispatch(getTodos());
    }, []);

    return (
        <div className="flex justify-center items-center h-screen">
            <div className="shadow-lg rounded-lg w-[500px]">
                <ListHeader
                    addItemToList={addItemToList}
                    title={title}
                    setTitle={setTitle}
                />
                <List
                    list={todo.todo_list}
                    removeItemFromList={removeItemFromList}
                    toggleItemDone={toggleItemDone}
                />
            </div>
        </div>
    );
}

export default TodoList;

Now, when you refresh the application it should look something like this if you have any todo items in the database.

React App Todo Backend
React App Todo Backend

You notice our item is marked as done since we toggled it while we were testing our toggle done endpoint using Postman.

We successfully loaded the list of all todo items from backend. In the next part we will make users able to add a new item to the database with provided title and automatically display them in the list.