Search code examples
javascriptreactjsreact-reduxredux-toolkit

How can I implement changing of a task, when the user clicks the button Save?


When the user clicks the 'Edit' button, the input field opens, allowing the user to type their new task. However, I'm unable to implement saving these changes when the user clicks the 'Save' button. Currently, when the user clicks 'Save,' the previous task is saved, but the new task disappears. In my Task component I created a useState that gets an initial state equal to the current state. I then created onSaveClicked which should change my current task to a new task. When I check with the Redux development tools, I can see that my payload is being updated, but it is not showing up in the UI.

import { ADD_TASK, REMOVE_TASK, UPDATE_TASK } from "../actionsTypes";
import { v4 as uuidv4 } from "uuid";

export const initialState = {
  tasklist: [], //tasklist
};

export function taskReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TASK:
      return {
        ...state,
        tasklist: [
          ...state.tasklist,
          {
            id: uuidv4(),
            title: action.payload,
            isCompleted: false,
            isEditing: false,
            status: "To do",
            number: state.tasklist.length + 1,
          },
        ],
      };

    case REMOVE_TASK:
      return {
        ...state,
        tasklist: state.tasklist.filter((todo) => action.payload !== todo.id),
      };

    case UPDATE_TASK:
      return {
        ...state,
        tasklist: state.tasklist.map((prevTask) =>
          prevTask.id === action.payload.id
            ? { ...prevTask, title: action.payload.newTitle }
            : prevTask
        ),
      };

    default:
      return state;
  }
}
import React, { useState } from "react";
import "./Task.css";
import Button from "@mui/material/Button";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import { useDispatch } from "react-redux";
import { updateTask } from "../../store/actions";

const Task = ({ task, onDelete }) => {
  const [isEditing, setIsEditing] = useState(false);
  const [inputValue, setInputValue] = useState(task.title);
  const dispatch = useDispatch();

  const onEdit = () => {
    setIsEditing(!isEditing);
  };

  const onSaveClicked = (title) => {
    dispatch(updateTask(title.id, inputValue));
    setIsEditing(!isEditing);
  };

  return (
    <li>
      {isEditing ? (
        <input
          id="edittask"
          type="text"
          value={inputValue}
          onChange={(e) => {
            // const newName = e.target.value; // setInputValue
            // dispatch(updateTask(task.id, newName));
            setInputValue(e.target.value);
          }}
        />
      ) : (
        <>
          {task.number}
          <p>{task.title}</p>
          {task.isCompleted}
          {task.status}
        </>
      )}
      {!isEditing ? (
        <Button onClick={onEdit} variant="outlined" endIcon={<EditIcon />}>
          Edit
        </Button>
      ) : (
        <Button onClick={onSaveClicked} variant="outlined">
          Save
        </Button>
      )}
      <Button onClick={onDelete} variant="outlined" endIcon={<DeleteIcon />}>
        Remove
      </Button>
    </li>
  );
};

export default Task;
import { ADD_TASK, REMOVE_TASK, UPDATE_TASK } from "./actionsTypes";

export const addTask = (title) => ({
  type: ADD_TASK,
  payload: title, //title
});

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

export const updateTask = (id, title) => ({
  type: UPDATE_TASK,
  payload: { id, newName: title },
});
import React from "react";
import InputTask from "../InputTask/InputTask";
import HeaderOfTaskList from "../HeaderOfTaskList/HeaderOfTaskList";
import Task from "../Task/Task";
import { useDispatch, useSelector } from "react-redux";
import { removeTask, addTask } from "../../../redux/store/actions";
import "./TaskList.css";

export const TaskList = () => {
  const dispatch = useDispatch();
  const task = useSelector((state) => state.tasklist);

  const handleDelete = (id) => {
    dispatch(removeTask(id));
  };

  const handleAddTask = (tasklist) => {
    dispatch(addTask(tasklist));
  };

  return (
    <div>
      <InputTask addTask={handleAddTask} />
      <HeaderOfTaskList />
      <ul>
        {task.map((task) => (
          <Task
            task={task}
            key={task.id}
            onDelete={() => handleDelete(task.id)}
          />
        ))}
      </ul>
    </div>
  );
};

I have the above snippets of code


Solution

  • Issue

    The issue it would seem is that you are accessing an id property of the button element's onClick event object and not the specific task object you are trying to update the title of.

    const onSaveClicked = (title) => { // <-- title is onClick event object
      dispatch(updateTask(title.id, inputValue));
      setIsEditing(!isEditing);
    };
    
    ...
    
    <Button onClick={onSaveClicked} variant="outlined">
      Save
    </Button>
    

    Solution

    You can access the current task prop value that is closed over in scope:

    const Task = ({ task, onDelete }) => {
      ...
    
      const onSaveClicked = () => {
        dispatch(updateTask(task.id, inputValue));
        setIsEditing(!isEditing);
      };
    
      ...
    
      <Button onClick={onSaveClicked} variant="outlined">
        Save
      </Button>
    
      ...
    };
    

    You could also explicitly pass the task reference to the onSaveClicked callback:

    const Task = ({ task, onDelete }) => {
      ...
    
      const onSaveClicked = (task) => {
        dispatch(updateTask(task.id, inputValue));
        setIsEditing(!isEditing);
      };
    
      ...
    
      <Button onClick={() => onSaveClicked(task)} variant="outlined">
        Save
      </Button>
    
      ...
    };