Ошибка типа: Tasks.map не является функцией (стек MERN)

Я работаю над приложением «список дел» стека MERN, которое выполняет операции CRUD. Когда я редактирую «задачу» из списка, я получаю Uncaught TypeError: tasks.map is not a function после того, как нажимаю кнопку «Сохранить». Я читал здесь похожие темы и пробовал несколько решений, но так и не смог это исправить.

Я делюсь соответствующими фрагментами кода и надеюсь, что кто-нибудь сможет мне немного помочь. Ваше время и усилия будут оценены по достоинству.

В моей папке client компонент SaveTask.jsx:

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.info("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.info("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.info(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 компонент:

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.info("Task ID to remove:", _id);
    
        try {
            const res = await axios.get("/task/removeTask", {
                headers: {
                    Authorization: `Bearer ${userToken}`
                },
                params: {
                    _id
                }
            });
            console.info("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.info('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.info('Editing task:', { task }); // Log the task details being edited
        // Dispatch an action to set the task for editing
        console.info('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 компонент:

Кроме того, этот файл возвращал то же самое TypeError: tasks.map is not a function. Однако это исправлено после того, как я добавил Array.isArray(tasks). Я не могу адаптировать его к текущей ошибке.

import React, { useContext } from 'react';
import Task from './Task';
import TaskContext from '../context/TaskContext';

function AllTask() {
    const { tasks, editMode, taskToEdit } = useContext(TaskContext);
    
    console.info('Tasks:', tasks);
    console.info('EditMode:', editMode);
    console.info('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 файл:

Кажется, что task.map в case "EDIT_TASK": является корнем ошибки. case "MARK_DONE": имеет ту же структуру, но успешно работает.

function taskReducer(tasks = [], action) {
    console.info("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.info("Tasks before removal:", tasks);
            const updatedTasks = tasks.filter((task) => task._id !== action._id);
            console.info("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.info('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.info('Processing CLEAR_EDIT_MODE');
            return {
                ...tasks,
                taskToEdit: null,
                editMode: false
            };
        }
        default: {
            throw Error("Unknown Action" + action.type)
        }
    }
}

export default taskReducer;

В моей папке server есть файл taskController.js:

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.info("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.info("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.info("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 }

Вот что я вижу в консоли браузера, когда сохраняю отредактированную задачу (задача успешно редактируется и сохраняется в базе данных):

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

Не могли бы вы добавить журнал в строку, где возникает ошибка, и проверить, какое значение имеет tasks? Вероятно, это object вместо array

Onur Doğan 05.05.2024 18:28

@onur-doğan Конечно. Вот что вернулось: Tasks before mapping: Object { 0: {…}, 1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}, 6: {…}, taskToEdit: {…}, editMode: true }

Rumeysa Gelgi 05.05.2024 18:33

@onur-doğan Спасибо. Я предполагал, что это не массив, но не знал, как с ним обращаться. Я исправил это сейчас. Я поделюсь своим ответом здесь.

Rumeysa Gelgi 05.05.2024 18:41

Я планировал упомянуть, что проблема, вероятно, связана с обоими диспетчерами SET_EDIT_MODE и CLEAR_EDIT_MODE, поскольку они устанавливают состояние как объект, а не массив, но вы уже это исправили. Тогда отлично, пожалуйста!

Onur Doğan 05.05.2024 18:48

@onur-doğan Извините, что снова вас беспокою. Можете ли вы проверить мой вопрос здесь, который относится к тому же приложению, и поделиться со мной своим мнением? Спасибо.

Rumeysa Gelgi 08.05.2024 16:16

Нет проблем, конечно. Я думаю, что проблема в обоих диспетчерах SET_EDIT_MODE и CLEAR_EDIT_MODE, поскольку в этих частях строка кода ...tasks преобразует массив задач в объект. Вероятно, это нарушает все остальные функции, поскольку задачи должны представлять собой массив. Таким образом, поля taskToEdit и editMode должны быть в каждом элементе массива как true/false, а не как объект. Если вы хотите отредактировать один из них, установите для этих полей значение true, в противном случае используйте их как false. НАПРИМЕР. Я думаю, вы можете установить taskToEdit: true для редактируемого элемента в массиве задач для SET_EDIT_MODE

Onur Doğan 08.05.2024 17:34

Надеюсь, это понятно и полезно. В противном случае дайте мне знать, пожалуйста, я постараюсь быть более полезным.

Onur Doğan 08.05.2024 17:35

@onur-doğan Надеюсь, я не ошибаюсь, но вы предлагаете мне добавить taskToEdit = boolean и editMode = boolean к каждому диспетчеру в taskReducer? Я также пробовал другой подход, например, изменение кода в EDIT_TASK, SET_EDIT_MODE и CLEAR_EDIT_MODE, учитывая, что ...tasks — это массив, но это также нарушило функциональность.

Rumeysa Gelgi 08.05.2024 20:50

Да, я предлагал вам нечто подобное. Потому что мы не можем хранить taskToEdit или editMode в массиве. Вообще-то проблема с редукторами вообще. На некоторых из них в контекст задается только массив задач. Для остальных вы устанавливаете все эти поля как объект. Я думаю, все они должны быть объектами и включать поля { tasks, editMode, taskToEdit. Например, для SET_EDIT_MODE он может обновить состояние, например { tasks, taskToEdit: action.payload, editMode: true}. Насколько я понимаю и другие компоненты, контекст должен включать эти три

Onur Doğan 10.05.2024 13:06

Когда вы использовали синтаксис распространения для задания задач в контексте, например ...tasks, он устанавливает задачи как объект, поэтому вам не следует использовать там .... Это решило бы некоторые проблемы. Но в другой части, например, в диспетчере MARK_DONE, он должен возвращать значение return { tasks: tasks.map(...), ... }, поскольку когда вы возвращаете сопоставленный массив, он по-прежнему устанавливает состояние контекста как массив, но это должен быть объект. Итак, я думаю, вы можете проверить все эти диспетчеры и убедиться, что все они обновляют состояния как объект. Итак, необходимо исправить это осложнение: состояние должно быть установлено как один тип (объект) для каждого диспетчера.

Onur Doğan 10.05.2024 13:11

@onur-doğan Спасибо за помощь. Я исправил проблему. Не так, как вы предлагали, но я преобразовал массив в AllTask.jsx в объект, и это решило проблему исчезновения задач. Возможно, это не лучший подход, в любом случае все четыре операции CRUD сейчас работают безупречно.

Rumeysa Gelgi 11.05.2024 18:31

Приятно это слышать, пожалуйста.

Onur Doğan 11.05.2024 20:13
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
12
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Спасибо пользователю @onur-doğan за то, что привели меня к решению.

Я проверил значение tasks с помощью оператора console.info, который вернулся как Tasks before mapping: Object { 0: {…}, 1: {…}, 2: {…}, 3: {…}, 4: {…}, 5: {…}, 6: {…}, taskToEdit: {…}, editMode: true }

Затем я отредактировал case "EDIT_TASK": в taskReducer.js, чтобы настроить доступ и обновление задач как объекта:

        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;
        }

Теперь «редактирование и сохранение задачи» работает как положено, как видно на консоли моего браузера:

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

Другие вопросы по теме