You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React类组件转函数组件报错:Tasks.map is not a function

类组件转函数组件报错:Tasks.map is not a function

问题背景

尝试将React Todo应用的类组件TasksList转换为函数组件后,页面加载失败,控制台抛出错误:Uncaught TypeError: Tasks.map is not a function,错误指向列表渲染时的Tasks.map代码段。

原始类组件代码

import React, { Component } from "react";
import TaskDataService from "../services/task.service";
import FadeIn from 'react-fade-in';
import { Link } from "react-router-dom";

export default class TasksList extends Component {
    constructor(props) {
        super(props);
        this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this);
        this.retrieveTasks = this.retrieveTasks.bind(this);
        this.refreshList = this.refreshList.bind(this);
        this.setActiveTask = this.setActiveTask.bind(this);
        this.removeAllTasks = this.removeAllTasks.bind(this);
        this.searchTitle = this.searchTitle.bind(this);

        this.state = {
            Tasks: [],
            currentTask: null,
            currentIndex: -1,
            searchTitle: ""
        };
    }

    componentDidMount() {
        this.retrieveTasks();
    }

    onChangeSearchTitle(e) {
        const searchTitle = e.target.value;

        this.setState({
            searchTitle: searchTitle
        });
    }

    retrieveTasks() {
        TaskDataService.getAll()
            .then(response => {
                this.setState({
                    Tasks: response.data
                });
                console.log(response.data);
            })
            .catch(e => {
                console.log(e);
            });
    }

    refreshList() {
        this.retrieveTasks();
        this.setState({
            currentTask: null,
            currentIndex: -1
        });
    }

    setActiveTask(Task, index) {
        this.setState({
            currentTask: Task,
            currentIndex: index
        });
    }

    removeAllTasks() {
        TaskDataService.deleteAll()
            .then(response => {
                console.log(response.data);
                this.refreshList();
            })
            .catch(e => {
                console.log(e);
            });
    }

    searchTitle() {
        this.setState({
            currentTask: null,
            currentIndex: -1
        });

        TaskDataService.findByTitle(this.state.searchTitle)
            .then(response => {
                this.setState({
                    Tasks: response.data
                });
                console.log(response.data);
            })
            .catch(e => {
                console.log(e);
            });
    }

    render() {
        const { searchTitle, Tasks, currentTask, currentIndex } = this.state;

        return (
            <FadeIn>
                <div className="list row">
                        <div className="col-md-8">
                            <div className="input-group mb-3">
                                <input
                                    type="text"
                                    className="form-control"
                                    placeholder="Search by title"
                                    value={searchTitle}
                                    onChange={this.onChangeSearchTitle}
                                />
                                <div className="input-group-append">
                                    <button
                                        className="btn btn-outline-secondary"
                                        type="button"
                                        onClick={this.searchTitle}
                                    >
                                        Search
                                    </button>
                                </div>
                            </div>
                        </div>
                        <div className="col-md-6">
                            <h4>Tasks List</h4>

                            <FadeIn>
                                <ul className="list-group">
                                    {Tasks &&
                                        Tasks.map((Task, index) => (
                                            <li
                                                className={
                                                    "list-group-item " +
                                                    (index === currentIndex ? "active" : "")
                                                }
                                                onClick={() => this.setActiveTask(Task, index)}
                                                key={index}
                                            >
                                                <div className="align-left">
                                                    {Task.title}
                                                </div>

                                                <div className="align-right">
                                                    {Task.startDate.toString().split("T")[0]}
                                                </div>
                                            </li>
                                        ))}
                                </ul>
                            </FadeIn>

                            <button
                                className="m-3 btn btn-sm btn-danger"
                                onClick={this.removeAllTasks}
                            >
                                Remove All
                            </button>
                        </div>
                        <div className="col-md-6">
                            {currentTask ? (
                                <FadeIn>
                                    <div>
                                        <h4>Task</h4>
                                        <div>
                                            <label>
                                                <strong>Title:</strong>
                                            </label>{" "}
                                            {currentTask.title}
                                        </div>
                                        <div>
                                            <label>
                                                <strong>Description:</strong>
                                            </label>{" "}
                                            {currentTask.description}
                                        </div>
                                        <div>
                                            <label>
                                                <strong>Status:</strong>
                                            </label>{" "}
                                            {currentTask.completed ? "Completed" : "Pending"}
                                        </div>
                                        <div>
                                            <label>
                                                <strong>Due Date:</strong>
                                            </label>{" "}
                                            {currentTask.startDate.split("T")[0]}
                                        </div>

                                        <Link
                                            to={"/Tasks/" + currentTask.id}
                                            className="badge badge-warning"
                                        >
                                            Edit
                                        </Link>
                                    </div>
                                </FadeIn>
                            ) : (
                                <div>
                                    <br />
                                    <p>Please click on a Task...</p>
                                </div>
                            )}
                        </div>
                </div>
            </FadeIn>
        );
    }
}

转换后的函数组件(存在错误)

import React, { useState, useEffect } from "react";
import TaskDataService from "../services/task.service";
import FadeIn from 'react-fade-in';
import { Link } from "react-router-dom";


function TasksList() {
    const [Tasks, setTasks] = useState([]);
    const [currentTask, setCurrentTask] = useState(null);
    const [currentIndex, setCurrentIndex] = useState(-1);
    const [searchTitle, setSearchTitle] = useState("");

    useEffect(() => {
        retrieveTasks();
    });

    const onChangeSearchTitle = (e) => {
        const searchTitle = e.target.value;

        setSearchTitle({
            searchTitle: searchTitle
        });
    }

    const retrieveTasks = () => {
        TaskDataService.getAll()
            .then(response => {
                setTasks({
                    Tasks: response.data
                });
                console.log(response.data);
            })
            .catch(e => {
                console.log(e);
            });
    }

    const refreshList = () => {
        retrieveTasks();
        setTasks(null);
        setCurrentIndex(-1);
    }

    const setActiveTask = (Task, index) => {
        setTasks( Task );
        setCurrentIndex( index );
    }

    const removeAllTasks = () => {
        TaskDataService.deleteAll()
            .then(response => {
                console.log(response.data);
                refreshList();
            })
            .catch(e => {
                console.log(e);
            });
    }

    const onSearchTitle = () => {
        setTasks(null);
        setCurrentIndex(-1);

        TaskDataService.findByTitle(searchTitle)
            .then(response => {
                setTasks(response.data)
                console.log(response.data);
            })
            .catch(e => {
                console.log(e);
            });
    }

    return (
        <FadeIn>
            <div className="list row">
                <div className="col-md-8">
                    <div className="input-group mb-3">
                        <input
                            type="text"
                            className="form-control"
                            placeholder="Search by title"
                            value={searchTitle}
                            onChange={onChangeSearchTitle}
                        />
                        <div className="input-group-append">
                            <button
                                className="btn btn-outline-secondary"
                                type="button"
                                onClick={onSearchTitle}
                            >
                                Search
                            </button>
                        </div>
                    </div>
                </div>
                <div className="col-md-6">
                    <h4>Tasks List</h4>

                    <FadeIn>
                        <ul className="list-group">
                            {Tasks &&
                                Tasks.map((Task, index) => (
                                    <li
                                        className={
                                            "list-group-item " +
                                            (index === currentIndex ? "active" : "")
                                        }
                                        onClick={() => setActiveTask(Task, index)}
                                        key={index}
                                    >
                                        <div className="align-left">
                                            {Task.title}
                                        </div>

                                        <div className="align-right">
                                            {Task.startDate.toString().split("T")[0]}
                                        </div>
                                    </li>
                                ))}
                        </ul>
                    </FadeIn>

                    <button
                        className="m-3 btn btn-sm btn-danger"
                        onClick={removeAllTasks}
                    >
                        Remove All
                    </button>
                </div>
                <div className="col-md-6">
                    {currentTask ? (
                        <FadeIn>
                            <div>
                                <h4>Task</h4>
                                <div>
                                    <label>
                                        <strong>Title:</strong>
                                    </label>{" "}
                                    {currentTask.title}
                                </div>
                                <div>
                                    <label>
                                        <strong>Description:</strong>
                                    </label>{" "}
                                    {currentTask.description}
                                </div>
                                <div>
                                    <label>
                                        <strong>Status:</strong>
                                    </label>{" "}
                                    {currentTask.completed ? "Completed" : "Pending"}
                                </div>
                                <div>
                                    <label>
                                        <strong>Due Date:</strong>
                                    </label>{" "}
                                    {currentTask.startDate.split("T")[0]}
                                </div>

                                <Link
                                    to={"/tasks/" + currentTask.id}
                                    className="badge badge-warning"
                                >
                                    Edit
                                </Link>
                            </div>
                        </FadeIn>
                    ) : (
                        <div>
                            <br />
                            <p>Please click on a Task...</p>
                        </div>
                    )}
                </div>
            </div>
        </FadeIn>
    );
}

export default TasksList;

错误原因分析

  1. useEffect无限循环:useEffect未传入依赖数组,组件每次渲染都会调用retrieveTasks,导致状态反复更新。
  2. 状态更新格式错误:
    • setSearchTitle应该直接传入字符串值,而非对象{searchTitle: searchTitle},导致searchTitle变成对象,输入框无法正常显示。
    • retrieveTasks中setTasks错误传入对象{Tasks: response.data},而接口返回的response.data是数组,导致Tasks变成对象而非数组,无法调用map方法。
  3. 错误重置Tasks状态:refreshList和onSearchTitle中调用setTasks(null),将Tasks从数组变为null,后续渲染时Tasks && Tasks.map中Tasks是null,虽然null是真值,但null.map不存在,触发报错。
  4. setActiveTask逻辑错误:应该更新currentTask状态,而非用setTasks替换整个任务数组为单个任务对象,导致Tasks变成单个对象,无法map。

修正后的函数组件代码

import React, { useState, useEffect } from "react";
import TaskDataService from "../services/task.service";
import FadeIn from 'react-fade-in';
import { Link } from "react-router-dom";

function TasksList() {
    const [tasks, setTasks] = useState([]);
    const [currentTask, setCurrentTask] = useState(null);
    const [currentIndex, setCurrentIndex] = useState(-1);
    const [searchTitle, setSearchTitle] = useState("");

    // 仅在组件挂载时调用一次,对应componentDidMount
    useEffect(() => {
        retrieveTasks();
    }, []);

    const onChangeSearchTitle = (e) => {
        setSearchTitle(e.target.value);
    }

    const retrieveTasks = () => {
        TaskDataService.getAll()
            .then(response => {
                setTasks(response.data);
                console.log(response.data);
            })
            .catch(e => {
                console.log(e);
            });
    }

    const refreshList = () => {
        retrieveTasks();
        setCurrentTask(null);
        setCurrentIndex(-1);
    }

    const setActiveTask = (task, index) => {
        setCurrentTask(task);
        setCurrentIndex(index);
    }

    const removeAllTasks = () => {
        TaskDataService.deleteAll()
            .then(response => {
                console.log(response.data);
                refreshList();
            })
            .catch(e => {
                console.log(e);
            });
    }

    const onSearchTitle = () => {
        setCurrentTask(null);
        setCurrentIndex(-1);

        TaskDataService.findByTitle(searchTitle)
            .then(response => {
                setTasks(response.data);
                console.log(response.data);
            })
            .catch(e => {
                console.log(e);
            });
    }

    return (
        <FadeIn>
            <div className="list row">
                <div className="col-md-8">
                    <div className="input-group mb-3">
                        <input
                            type="text"
                            className="form-control"
                            placeholder="Search by title"
                            value={searchTitle}
                            onChange={onChangeSearchTitle}
                        />
                        <div className="input-group-append">
                            <button
                                className="btn btn-outline-secondary"
                                type="button"
                                onClick={onSearchTitle}
                            >
                                Search
                            </button>
                        </div>
                    </div>
                </div>
                <div className="col-md-6">
                    <h4>Tasks List</h4>

                    <FadeIn>
                        <ul className="list-group">
                            {tasks && tasks.length > 0 &&
                                tasks.map((task, index) => (
                                    <li
                                        className={`list-group-item ${index === currentIndex ? "active" : ""}`}
                                        onClick={() => setActiveTask(task, index)}
                                        key={task.id || index} // 优先用task.id作为key,更可靠
                                    >
                                        <div className="align-left">
                                            {task.title}
                                        </div>

                                        <div className="align-right">
                                            {task.startDate.toString().split("T")[0]}
                                        </div>
                                    </li>
                                ))}
                        </ul>
                    </FadeIn>

                    <button
                        className="m-3 btn btn-sm btn-danger"
                        onClick={removeAllTasks}
                    >
                        Remove All
                    </button>
                </div>
                <div className="col-md-6">
                    {currentTask ? (
                        <FadeIn>
                            <div>
                                <h4>Task</h4>
                                <div>
                                    <label>
                                        <strong>Title:</strong>
                                    </label>{" "}
                                    {currentTask.title}
                                </div>
                                <div>
                                    <label>
                                        <strong>Description:</strong>
                                    </label>{" "}
                                    {currentTask.description}
                                </div>
                                <div>
                                    <label>
                                        <strong>Status:</strong>
                                    </label>{" "}
                                    {currentTask.completed ? "Completed" : "Pending"}
                                </div>
                                <div>
                                    <label>
                                        <strong>Due Date:</strong>
                                    </label>{" "}
                                    {currentTask.startDate.split("T")[0]}
                                </div>

                                <Link
                                    to={`/tasks/${currentTask.id}`}
                                    className="badge badge-warning"
                                >
                                    Edit
                                </Link>
                            </div>
                        </FadeIn>
                    ) : (
                        <div>
                            <br />
                            <p>Please click on a Task...</p>
                        </div>
                    )}
                </div>
            </div>
        </FadeIn>
    );
}

export default TasksList;

额外优化点:

  • 将Tasks变量名改为小写tasks,符合JavaScript变量命名规范
  • 列表项key优先使用task.id而非索引,避免列表更新时的性能问题和渲染错误
  • 用模板字符串简化className拼接
  • 增加tasks.length > 0判断,避免空数组时的不必要渲染

内容的提问来源于stack exchange,提问作者alphatango165

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 14:45:33