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

MERN Todo应用中React组件未按需重新渲染问题求助

MERN待办应用:新增任务后TodoList不重新渲染问题排查

问题描述

我正在开发一个基于MERN架构的待办事项应用,新增任务可以成功写入数据库,但TodoList组件不会自动重新渲染展示新任务。已经尝试用useState和useEffect触发重渲染,但没效果,且useEffect能正常获取到数据,求排查原因。

代码片段

App主文件

import React, {useState, useEffect} from 'react';
import './App.css';
import TodoHeader from './components/TodoHeader';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';

function App() {
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('http://localhost:8080/fetch_records');
        const data = await response.json();
        setTasks(data);
        console.log("Fetched data", data)
      } catch (error) {
        console.error('Error fetching tasks:', error);
      }
    };

    fetchData();
  }, []);
    

  return (
    <div className="App">
      <TodoHeader />
      <TodoForm />
      <TodoList item={{tasks}}/>
    </div>
  );
}

export default App;

TodoList组件

import React, {useState, useEffect} from 'react'
import './TodoList.css'

export default function TodoList({item}) {
    const [tasks, setTasks] = useState([])
       

    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch('http://localhost:8080/fetch_records')
            let data = await response.json()
            setTasks((prevTasks) => [...prevTasks, ...data]);
            //setTasks(data)
        }

        fetchData()
    }, [])

    return (
        <div>
        {/* Display the list of tasks */}
        <ul>
            {item.tasks.map((task) => (
            <li key={task._id}>{task.task} — {task.dueDate}</li>
            ))}
        </ul>
        </div>
    )
}

TodoForm组件

import React, { useState } from 'react'
import './TodoForm.css'
import TodoList from './TodoList';


export default function TodoForm() {
    const [tasks, setTasks] = useState([]);

    const handleSubmit = async (event) => {
        event.preventDefault(); // Prevent the default form submission behavior
        
    
        const response = await fetch('http://localhost:8080/todo-submit', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              task: event.target.task.value, // Access form input values using event.target
              dueDate: event.target.dueDate.value,
              isAccomplished: false
            }),
          });

        const selector = document.querySelector('.result');
        try {
            const contentType = response.headers.get('content-type');
            

            if (contentType && contentType.includes('application/json')) {
                const result = await response.json();
                

                 // Now you can use the 'result' object to dynamically update your UI
                if (result.success) {
                    selector.style.color = "green"
                    selector.insertAdjacentHTML("afterbegin", "<b>" + result.message + "</b>")
                    //console.log('Success:', result.message);
                    // Update your UI here, e.g., show a success message in a div

                    const tasksResponse = await fetch('http://localhost:8080/fetch_records'); // Replace with your actual API endpoint
                    const tasksData = await tasksResponse.json();
                    setTasks(tasksData);
                } else {
                    selector.style.color = "red"
                    selector.insertAdjacentHTML("afterbegin", "<b>" + result.errorMessage + "</b>")
                    //console.error('Error:', result.errorMessage);
                    // Handle the error, e.g., show an error message in a div
                }
            }

            else {
                const nonJsonResponse = await response.text();
                selector.style.color = "red"
                selector.insertAdjacentHTML("afterbegin", "<u>" + nonJsonResponse + "</u>")
            }

    
        
        } catch (error) {
            selector.style.color = "red"
            selector.insertAdjacentHTML("afterbegin", "<b><u>" + error + "</u></b>")
          //console.error('Error:', error);
          // Handle unexpected errors, e.g., show a generic error message in a div
        }
      };

    return(
        <>
            <form action="http://localhost:8080/todo-submit" method="post" onSubmit={handleSubmit}>
                <div class="input-group">
                    <span class="input-group-text">Enter your task to be done</span>
                    <textarea class="form-control" aria-label="With textarea" name="task" />
                </div>
                <div class="input-group">
                    <span class="input-group-text">Choose the due date for this task: </span>
                    <input type="date" class="form-control" name="dueDate" />
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Submit data</button>
                </div>
            </form>
            <div className="result form-control"></div>
    </>
    )
}

问题原因分析

  1. 状态分散管理:App、TodoList、TodoForm各自维护独立的tasks状态,TodoForm提交后更新的是自身内部的tasks,与App中的tasks无关联,导致App状态不刷新,TodoList接收的props也不会变化。
  2. TodoList冗余逻辑:TodoList内部定义了tasks状态和挂载时的fetch逻辑,但渲染时实际使用的是App传递的item.tasks,内部状态完全未被利用;同时useEffect仅在组件挂载时执行一次,后续不会重新触发。
  3. 直接操作DOM:TodoForm中通过document.querySelector直接操作DOM显示提示,不符合React状态管理模式,代码耦合度高。

修复方案

1. 统一状态到App组件

所有任务列表相关状态由App统一管理,子组件通过props接收状态和更新回调,实现单向数据流。

修改后的App组件

import React, {useState, useEffect} from 'react';
import './App.css';
import TodoHeader from './components/TodoHeader';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';

function App() {
  const [tasks, setTasks] = useState([]);

  // 封装获取任务的逻辑,方便复用
  const fetchTasks = async () => {
    try {
      const response = await fetch('http://localhost:8080/fetch_records');
      const data = await response.json();
      setTasks(data);
      console.log("Fetched data", data)
    } catch (error) {
      console.error('Error fetching tasks:', error);
    }
  };

  useEffect(() => {
    fetchTasks();
  }, []);
    

  return (
    <div className="App">
      <TodoHeader />
      {/* 传递fetchTasks给TodoForm,提交成功后触发更新 */}
      <TodoForm onTaskAdded={fetchTasks} />
      {/* 直接传递tasks,简化props */}
      <TodoList tasks={tasks}/>
    </div>
  );
}

export default App;

修改后的TodoList组件

移除内部冗余状态和fetch逻辑,直接使用props传递的tasks,props变化时组件自动重新渲染:

import React from 'react'
import './TodoList.css'

export default function TodoList({tasks}) {
    return (
        <div>
        <ul>
            {tasks.map((task) => (
            <li key={task._id}>{task.task} — {task.dueDate}</li>
            ))}
        </ul>
        </div>
    )
}

修改后的TodoForm组件

移除内部tasks状态,用React状态管理提示信息,避免直接操作DOM,提交成功后调用App传递的onTaskAdded回调:

import React, { useState } from 'react'
import './TodoForm.css'

export default function TodoForm({onTaskAdded}) {
    const [message, setMessage] = useState('');
    const [messageStyle, setMessageStyle] = useState({});

    const handleSubmit = async (event) => {
        event.preventDefault();
        setMessage(''); // 清空之前的提示

        const response = await fetch('http://localhost:8080/todo-submit', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              task: event.target.task.value,
              dueDate: event.target.dueDate.value,
              isAccomplished: false
            }),
          });

        try {
            const contentType = response.headers.get('content-type');

            if (contentType && contentType.includes('application/json')) {
                const result = await response.json();
                if (result.success) {
                    setMessage(`<b>${result.message}</b>`);
                    setMessageStyle({color: 'green'});
                    // 调用回调通知App更新任务列表
                    await onTaskAdded();
                    // 清空表单
                    event.target.reset();
                } else {
                    setMessage(`<b>${result.errorMessage}</b>`);
                    setMessageStyle({color: 'red'});
                }
            } else {
                const nonJsonResponse = await response.text();
                setMessage(`<u>${nonJsonResponse}</u>`);
                setMessageStyle({color: 'red'});
            }
        } catch (error) {
            setMessage(`<b><u>${error}</u></b>`);
            setMessageStyle({color: 'red'});
        }
      };

    return(
        <>
            <form onSubmit={handleSubmit}>
                <div className="input-group">
                    <span className="input-group-text">Enter your task to be done</span>
                    <textarea className="form-control" aria-label="With textarea" name="task" />
                </div>
                <div className="input-group">
                    <span className="input-group-text">Choose the due date for this task: </span>
                    <input type="date" className="form-control" name="dueDate" />
                </div>
                <div className="form-group">
                    <button type="submit" className="btn btn-primary">Submit data</button>
                </div>
            </form>
            <div className="result form-control" style={messageStyle} dangerouslySetInnerHTML={{__html: message}}></div>
        </>
    )
}

修复说明

  • 状态统一由App管理,TodoForm提交成功后触发App的fetchTasks更新tasks状态,TodoList接收的props变化后自动重新渲染。
  • 移除TodoList内部冗余的状态和fetch逻辑,简化组件职责。
  • 用React状态管理提示信息,替代直接操作DOM,符合React开发模式。

内容的提问来源于stack exchange,提问作者m.kreyn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 15:24:59