Search code examples
node.jsreactjsexpressreduxredux-toolkit

Todo not being updated in fullstack todolist app - Redux Toolkit


I am building a fullstack todolist app and when trying to update a todo, nothing occurs. As shown in Todo.js, I have tried to manually change the value of todo to test the change. When logging the action.payload to the console, it just return the original value. I am dispatching updateTodo in the handleEdit function. I have also checked from the backend and everything works fine when testing in Postman.

Todo.js

import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { deleteTodo, getTodos, reset, updateTodo } from "../features/todoSlice";
import { toast } from "react-toastify";

export const Todo = ({ todo, removeTodo }) => {
  const [task, setTask] = useState({
    todo: "task three test",
    completed: todo.completed,
    isEditing: todo.isEditing,
  });

  const dispatch = useDispatch();
  const { isSuccess, isError, message, todos } = useSelector(
    (state) => state.todo
  );

  const handleEdit = (c) => {
    if (c) {
      dispatch(updateTodo({ todoId: todo._id, task }));
    }
  };

  const handleClick = () => {
    dispatch(deleteTodo(todo._id));

    if (isError) {
      toast.error(`${message}`, {
        position: "top-right",
        autoClose: 1000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: false,
        draggable: false,
        progress: undefined,
        theme: "colored",
      });
    }

    if (isSuccess) {
      toast.success("Todo deleted", {
        position: "top-right",
        autoClose: 1000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: false,
        draggable: false,
        progress: undefined,
        theme: "colored",
      });
    }
  };
  return (
    <div className="Todo">
      <p>{todo.task}</p>
      <div className="todo-actions">
        <i className="fa-solid fa-pen-to-square"></i>
        <i className="fa-sharp fa-solid fa-trash" onClick={handleClick}></i>
        <div className="check-wrapper" onClick={(c) => handleEdit(c)}>
          {todo.completed && (
            <div className="check">
              <i className="fa-sharp fa-solid fa-check"></i>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

todoService.js

import axios from "axios";

const API_URL = "/api/todos/";

// Create todo
const createTodo = async (todoData) => {
  const response = await axios.post(API_URL, todoData);

  return response.data;
};

// Get todos
const getTodos = async () => {
  const response = await axios.get(API_URL);

  return response.data;
};

// Delete todos
const deleteTodo = async (todoId) => {
  const response = await axios.delete(API_URL + todoId);

  return response.data;
};

// Edit todos
const updateTodo = async (todoId, todoData) => {
  const response = await axios.put(API_URL + todoId, todoData);

  return response.data;
};

const todoService = {
  createTodo,
  getTodos,
  deleteTodo,
  updateTodo,
};

export default todoService;

todoSlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import todoService from "./todoService";
import { toast } from "react-toastify";
const initialState = {
  todos: [],
  isError: false,
  isSuccess: false,
  isLoading: false,
  message: "",
};

// Create new todo
export const createTodo = createAsyncThunk(
  "todos/create",
  async (todoData, thunkAPI) => {
    try {
      return await todoService.createTodo(todoData);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Get todos
export const getTodos = createAsyncThunk(
  "todos/getAll",
  async (_, thunkAPI) => {
    try {
      return await todoService.getTodos();
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Delete todo
export const deleteTodo = createAsyncThunk(
  "todos/delete",
  async (id, thunkAPI) => {
    try {
      return await todoService.deleteTodo(id);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Update todo
export const updateTodo = createAsyncThunk(
  "todos/update",
  async ({ todoId, todoData }, thunkAPI) => {
    try {
      return await todoService.updateTodo(todoId, todoData);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

export const todoSlice = createSlice({
  name: "todo",
  initialState,
  extraReducers: (builder) => {
    builder
      .addCase(createTodo.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(createTodo.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.todos.push(action.payload);
      })
      .addCase(createTodo.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getTodos.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(getTodos.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.todos = action.payload;
      })
      .addCase(getTodos.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(deleteTodo.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(deleteTodo.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.todos = state.todos.filter((todo) => todo._id !== action.payload);
      })
      .addCase(deleteTodo.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(updateTodo.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(updateTodo.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        console.log(action.payload);
        state.todos = state.todos.map((todo) =>
          todo._id === action.payload._id
            ? {
                ...todo,
                task: action.payload.task,
                completed: action.payload.completed,
                isEditing: action.payload.isEditing,
              }
            : todo
        );
      })
      .addCase(updateTodo.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      });
  },
});

export const { reset } = todoSlice.actions;
export default todoSlice.reducer;

// @desc    edit todo / toggle complete / isEditing
// @route   PUT /api/todos
// @access  Public
const editTodo = asyncHandler(async (req, res) => {
  const todo = await Todo.findById(req.params.id);

  if (!todo) {
    res.status(400);
    throw new Error("Todo not found");
  }

  const updatedTodo = await Todo.findByIdAndUpdate(req.params.id, req.body, {
    new: true,
  });

  res.status(200).json(updatedTodo);

Solution

  • Seems like I needed to set task to todoData.

     const handleEdit = (c) => {
        if (c) {
          dispatch(updateTodo({ todoId: todo._id, todoData: task }));
        }
      };