I’m working on a MERN stack “to-do list” application which performs CRUD operations. When I edit a "task" from the list, I receive Uncaught TypeError: tasks.map is not a function
after I click on Save button. I’ve read similar topics here and tried multiple solutions but I still can’t fix it.
I’m sharing related code snippets and I hope someone can help me a bit. Your time and effort will be appreciated.
In my client
folder, SaveTask.jsx
component:
import React, { useState, useEffect, useContext } from 'react';
import TaskContext from '../context/TaskContext';
import TokenContext from '../context/TokenContext';
import axios from "../axios/axios";
import "../styles/saveTask.css";
function SaveTask() {
const { tasks, dispatch } = useContext(TaskContext);
const { userToken } = useContext(TokenContext);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const { editMode, taskToEdit } = tasks; // Extract editMode and taskToEdit from context
useEffect(() => {
// Populate form fields with task details when in editMode
if (editMode && taskToEdit) {
setTitle(taskToEdit.title);
setDescription(taskToEdit.description);
} else {
setTitle(""); // Reset title when not editing
setDescription(""); // Reset description when not editing
}
}, [editMode, taskToEdit]);
const handleAddOrEdit = async (e) => {
e.preventDefault();
try {
if (editMode && taskToEdit) {
// Update existing task
const res = await axios.post(`/task/editTask/${taskToEdit._id}`, { title, description }, {
headers: {
Authorization: `Bearer ${userToken}`
}
});
console.log("Task edited:", res.data);
// Update task in context
dispatch({
type: 'EDIT_TASK',
_id: taskToEdit._id,
title: res.data.task.title,
description: res.data.task.description
});
dispatch({ type: 'CLEAR_EDIT_MODE' }); // Clear edit mode after submission
} else {
// Add new task
const res = await axios.post("/task/addTask", { title, description }, {
headers: {
Authorization: `Bearer ${userToken}`
}
});
console.log("New task added:", res.data);
// Add new task to context
dispatch({
type: "ADD_TASK",
_id: res.data.task._id,
title: res.data.task.title,
description: res.data.task.description,
});
}
// Reset form fields
setTitle("");
setDescription("");
} catch (error) {
console.log(error);
}
};
return (
<div className="addContainer md:w-1/3 md:mx-auto mx-3 mt-3 flex justify-center">
<div className='w-11/12'>
<form onSubmit={handleAddOrEdit}>
<div>
<label htmlFor="title">Title</label>
<input
type="text"
name="title"
id="title"
value={title}
required
onChange={(e) => setTitle(e.target.value)}
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
/>
</div>
<div className='my-3'>
<label htmlFor="description">Description</label>
<textarea
rows={5}
name="description"
id="description"
value={description}
required
onChange={(e) => setDescription(e.target.value)}
style={{ resize: "none" }}
className='bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
/>
</div>
<div className='flex justify-center'>
<button
type='submit'
className='bg-blue-700 rounded-md text-white px-5 py-1'
>
{editMode ? "Save" : "Add"}
</button>
</div>
</form>
<div className="toast bg-green-600 text-white p-3 rounded-xl shadow-2xl text-center absolute bottom-4 left-1/2 -translate-x-1/2" id='toast'>
<p>This is test</p>
</div>
</div>
</div>
);
}
export default SaveTask;
Task.jsx
component:
import React, { useContext } from 'react';
import moment from 'moment';
import "../styles/task.css";
import axios from "../axios/axios.js"
import TaskContext from '../context/TaskContext.js';
import TokenContext from '../context/TokenContext.js';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
function Task({ task }) {
const { _id, title, description, completed } = task;
const { dispatch } = useContext(TaskContext);
const { userToken } = useContext(TokenContext);
const handleRemove = async (e) => {
e.preventDefault();
console.log("Task ID to remove:", _id);
try {
const res = await axios.get("/task/removeTask", {
headers: {
Authorization: `Bearer ${userToken}`
},
params: {
_id
}
});
console.log("Task deletion response:", res.data);
dispatch({
type: "REMOVE_TASK",
_id
});
} catch (error) {
console.error("Error removing task:", error);
// Handle error state or display an error message to the user
}
}
const handleMarkDone = async () => {
try {
const res = await axios.post('/task/updateTaskStatus', {
_id: task._id,
completed: !task.completed // Toggle completion status
}, {
headers: {
Authorization: `Bearer ${userToken}` // Include JWT token in headers
}
}
);
console.log('Task completion status successfully updated:', res.data);
dispatch({
type: 'MARK_DONE',
_id: task._id
});
} catch (error) {
console.error('Error updating task:', error);
// Handle error state or display an error message to the user
}
};
const handleEdit = () => {
console.log('Editing task:', { task }); // Log the task details being edited
// Dispatch an action to set the task for editing
console.log('Dispatching SET_EDIT_MODE with task:', task);
dispatch({
type: 'SET_EDIT_MODE',
payload: task // Send the entire task object for editing
});
};
... // Rest of the code for UI, etc.
export default Task;
AllTask.jsx
component:
Also this file was returning the same
TypeError: tasks.map is not a function
. However it is fixed after I addedArray.isArray(tasks)
. I can't adapt it to the current error.
import React, { useContext } from 'react';
import Task from './Task';
import TaskContext from '../context/TaskContext';
function AllTask() {
const { tasks, editMode, taskToEdit } = useContext(TaskContext);
console.log('Tasks:', tasks);
console.log('EditMode:', editMode);
console.log('TaskToEdit:', taskToEdit);
return (
<div>
{Array.isArray(tasks) && tasks.length !== 0 ? (
tasks.map((task, index) => (
<Task key={index} task={task} id={index} />
))
) : (
<h1>No Tasks Found</h1>
)}
</div>
);
}
export default AllTask;
taskReducer.js
file:
It seems that the
task.map
incase "EDIT_TASK":
is the root of the error.case "MARK_DONE":
has the same structure but successfully works.
function taskReducer(tasks = [], action) {
console.log("taskreducer");
switch (action.type) {
// eslint-disable-next-line no-lone-blocks
case "ADD_TASK": {
return [
...tasks,
{
_id: action._id,
title: action.title,
description: action.description,
completed: false
}
]
}
case "SET_TASK": {
return action.payload
}
case "REMOVE_TASK": {
console.log("Tasks before removal:", tasks);
const updatedTasks = tasks.filter((task) => task._id !== action._id);
console.log("Tasks after removal:", updatedTasks);
return updatedTasks;
}
case "MARK_DONE": {
return tasks.map((task) => {
if (task._id === action._id) {
return {
...task,
completed: !task.completed
}
}
return task
})
}
case "EDIT_TASK": {
return tasks.map((task) => {
if (task._id === action._id) {
return {
...task,
title: action.title,
description: action.description
};
}
return task;
});
}
case 'SET_EDIT_MODE': {
console.log('Processing SET_EDIT_MODE:', action.payload);
return {
...tasks,
taskToEdit: action.payload, // Set the task for editing
editMode: true // Set edit mode to true
};
}
case "CLEAR_EDIT_MODE": {
console.log('Processing CLEAR_EDIT_MODE');
return {
...tasks,
taskToEdit: null,
editMode: false
};
}
default: {
throw Error("Unknown Action" + action.type)
}
}
}
export default taskReducer;
In my server
folder, taskController.js
file:
import taskModel from "../models/taskModel.js";
import userModel from "../models/userModel.js";
import dotenv from "dotenv";
import mongoose from "mongoose";
dotenv.config();
const addTask = async (req, res) => {
const { _id, title, description } = req.body;
const userId = req.user.id;
const user = await userModel.find({_id: userId});
if (!user) {
return res.status(404).json({ message: "User not found" });
}
const taskId = _id ? mongoose.Types.ObjectId(_id) : new mongoose.Types.ObjectId();
console.log("Task to be saved:", { taskId, title, description, completed: false, userId });
const newTask = new taskModel({ _id: taskId, title, description, completed: false, userId })
newTask.save()
.then((savedTask) => {
return (res.status(200).json({ message: "Task added successfully", task: savedTask }))
})
.catch((error) => {
return (
res.status(500).json({ message: error.message })
)
}
)
}
const removeTask = (req, res) => {
const { _id } = req.query;
console.log("Task ID to remove:", _id); // Log the ID being used for deletion
taskModel.findByIdAndDelete( _id )
.then((deletedTask) => {
if (!deletedTask) {
return res.status(404).json({ message: "Task not found" });
}
console.log("Deleted task:", deletedTask); // Log the deleted task
return res.status(200).json({ message: "Task deleted successfully" });
})
.catch((error) => {
console.error("Error deleting task:", error); // Log any errors
return res.status(500).json({ message: "Internal server error" });
});
}
const getTask = (req, res) => {
taskModel.find({ userId: req.user.id })
.lean() // Convert Mongoose documents to plain JavaScript objects
.then((data) => res.status(200).json(data))
.catch((error) => res.status(501).json({ message: error.message }))
}
const updateTaskStatus = async (req, res) => {
const { _id, completed } = req.body;
try {
// Find the task by _id and update the completed field
const updatedTask = await taskModel.findByIdAndUpdate(_id, { completed }, { new: true });
if (!updatedTask) {
return res.status(404).json({ message: 'Task not found' });
}
res.json(updatedTask); // Return the updated task
} catch (error) {
console.error('Error updating task completion:', error);
res.status(500).json({ message: 'Failed to update task completion status' });
}
};
const editTask = async (req, res) => {
const _id = req.params._id;
const { title, description } = req.body;
try {
const updatedTask = await taskModel.findByIdAndUpdate(
_id,
{ title, description },
{ new: true }
);
if (!updatedTask) {
return res.status(404).json({ message: "Task not found" });
}
return res.status(200).json({ message: "Task edited successfully", task: updatedTask });
} catch (error) {
console.error("Error updating task:", error);
return res.status(500).json({ message: "Internal server error" });
}
};
export { addTask, getTask, removeTask, updateTaskStatus, editTask }
This is what I encounter on my browser console when I save an edited task (task gets successfully edited and saved to the database):
XHR OPTIONS http://localhost:8000/api/task/editTask/663502dc2799787a594e2d6e [HTTP/1.1 204 No Content 3ms]
XHR POST http://localhost:8000/api/task/editTask/663502dc2799787a594e2d6e [HTTP/1.1 200 OK 221ms]
Task edited: Object { message: "Task edited successfully", task: {…} } SaveTask.jsx:37
taskreducer taskReducer.js:2
Uncaught TypeError: tasks.map is not a function
taskReducer taskReducer.js:37
React 3
App App.js:19
React 11
workLoop scheduler.development.js:266
flushWork scheduler.development.js:239
performWorkUntilDeadline scheduler.development.js:533
js scheduler.development.js:571
js scheduler.development.js:633
factory react refresh:6
Webpack 24
Thank you user @onur-doğan for leading me to the solution.
I checked tasks
value with a console.log
statement which returned as Tasks before mapping: Object { 0: {…}, 1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}, 6: {…}, taskToEdit: {…}, editMode: true }
Then I edited case "EDIT_TASK":
in taskReducer.js
to adjust how to access and update tasks as an object:
case "EDIT_TASK": {
const { taskToEdit, title, description } = action;
if (tasks && tasks.taskToEdit) {
const updatedTask = {
...tasks.taskToEdit,
title,
description
};
return {
...tasks,
taskToEdit: {
...tasks.taskToEdit,
title,
description
},
[taskToEdit]: updatedTask
};
}
return tasks;
}
Now "editing and saving a task" works as expected, as seen on my browser console:
XHR OPTIONS http://localhost:8000/api/task/editTask/663502dc2799787a594e2d6e [HTTP/1.1 204 No Content 3ms]
XHR POST http://localhost:8000/api/task/editTask/663502dc2799787a594e2d6e [HTTP/1.1 200 OK 205ms]
Task edited: Object { message: "Task edited successfully", task: {…} } SaveTask.jsx:37
taskreducer taskReducer.js:2
Processing CLEAR_EDIT_MODE taskReducer.js:70