React日历待办应用页面数据传递及待办计数异常问题
问题:日历页面无法正确显示每日待办事项总数
我正在开发一款带有每日待办清单的日历Web应用,点击任意日期的链接,用户可添加和编辑当日待办事项,同时希望在日历页面显示每日待办事项总数(例如:1 To-Do: "show total numbers of to-dos for this day")。但尝试从localStorage调用todos.length时,始终显示0。
相关代码如下:
App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import Calendar from './components/Calendar'; import TodoList from './components/TodoList'; import React, { useState } from 'react'; const App = () => { return ( <div> <h1>Calendar + To-Do-List Project</h1> <Router> <Routes> <Route path="/" element={<Calendar />} /> <Route path="/todo/:day" element={<TodoList />} /> </Routes> </Router> </div> ); } export default App;
Calendar.jsx
import React from 'react'; import './calendar.css'; import moment from 'moment'; import { Link } from 'react-router-dom'; const Day = ({ number, id }) => { const [todos, setTodos] = React.useState(() => JSON.parse(localStorage.getItem(`todos-${id}`)) || []); return ( <div className="day"> {number} <Link to={`/todo/${number}`}> To-Do: {todos.length} </Link> </div> ); }; const Calendar = () => { const now = moment(); const daysInMonth = now.daysInMonth(); const monthStart = now.startOf('month').day() === 0 ? 7 : now.startOf('month').day(); const weeks = []; let week = []; for (let i = 1; i < monthStart; i++) { week.push(null); } for (let i = 1; i <= daysInMonth; i++) { week.push(i); if (week.length === 7) { weeks.push(week); week = []; } } if (week.length > 0) { while (week.length < 7) { week.push(null); } weeks.push(week); } const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; return ( <div className="calendar-container"> <div className="calendar-header"> {now.format('MMMM YYYY')} </div> <div className="calendar-days"> <div className="calendar-week"> {dayNames.map((dayName) => ( <div key={`day-${dayName}`} className="calendar-day-name"> {dayName} </div> ))} </div> {weeks.map((week, index) => ( <div key={`week-${index}`} className="calendar-week"> {week.map((day) => ( <div key={`day-${day}`} className="calendar-day"> {day && <Day number={day} id={day} />} </div> ))} </div> ))} </div> </div> ); }; export default Calendar;
TodoList.jsx
import React, { useState, useEffect } from 'react'; import './TodoList.css'; import { useParams } from 'react-router-dom'; import { NavLink } from "react-router-dom"; const TodoList = () => { const {day} = useParams(); const [todos, setTodos] = useState(() => JSON.parse(localStorage.getItem(`todos-${day}`)) || []); const [todo, setTodo] = useState(""); const [todoEditing, setTodoEdit] = useState(null); const [editingText, setEditText] = useState(""); useEffect(() => { const json = JSON.stringify(todos); localStorage.setItem(`todos-${day}`, json); }, [todos, day]); const addTodo = (e) => { e.preventDefault(); const newTodo = { id: new Date().getTime(), text: todo, completed: false, }; setTodos([...todos].concat(newTodo)); setTodo(""); }; const toggleCompleted = (id) => { let updatedTodos = [...todos].map((todo) => { if (todo.id === id) { todo.completed = !todo.completed; } return todo; }); setTodos(updatedTodos); }; const deleteTodo = (id) => { let updatedTodos = [...todos].filter((todo) => todo.id !== id); setTodos(updatedTodos); }; const submitEdits = (id) => { const updatedTodos = [...todos].map((todo) => { if (todo.id === id) { todo.text = editingText; } return todo; }); setTodos(updatedTodos); setTodoEdit(null); }; return ( <div className="todo-list-container"> <div align='right'> <NavLink to='/' style={{textDecoration: 'none', color: 'black'}}><button > Back to Calendar </button></NavLink> </div> <h2 className="todo-list-header">To-Dos for Today</h2> <form onSubmit={addTodo}> <input type="text" value={todo} onChange={(e) => setTodo(e.target.value)} /> <button>Add</button> </form> <ul className="todo-list"> {todos.map((todo) => ( <div key={todo.id} > <div > <input type="checkbox" id="completed" checked={todo.completed} onChange={() => toggleCompleted(todo.id)} /> {todo.id === todoEditing ? ( <input type="text" onChange={(e) => setEditText(e.target.value)} /> ) : ( <div><span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span></div> )} </div> <div > {todo.id === todoEditing ? ( <button onClick={() => submitEdits(todo.id)}>Submit</button> ) : ( <button onClick={() => setTodoEdit(todo.id)}>Edit</button> )} <button onClick={() => deleteTodo(todo.id)}>Delete</button> </div> </div> ))} </ul> </div> ); } export default TodoList;
原因分析
问题出在Day组件的状态初始化逻辑:
useState的初始化函数仅在组件第一次挂载时执行一次,之后即使localStorage中的数据更新,组件也不会主动重新读取最新数据。- 当从
TodoList页面返回日历页面时,React可能会复用Day组件实例,导致状态不会重新初始化,无法获取更新后的待办数据。
解决方案
修改Day组件,使用useEffect主动读取localStorage数据,确保每次组件挂载或日期id变化时都能获取最新的待办数据:
const Day = ({ number, id }) => { const [todos, setTodos] = React.useState([]); React.useEffect(() => { // 定义加载待办的函数 const loadTodos = () => { const savedTodos = JSON.parse(localStorage.getItem(`todos-${id}`)) || []; setTodos(savedTodos); }; // 组件挂载时加载最新数据 loadTodos(); // 监听localStorage的变化(处理跨标签页修改的情况) window.addEventListener('storage', loadTodos); // 组件卸载时移除监听,避免内存泄漏 return () => { window.removeEventListener('storage', loadTodos); }; }, [id]); // 当日期id变化时重新执行 return ( <div className="day"> {number} <Link to={`/todo/${number}`}> To-Do: {todos.length} </Link> </div> ); };
修改说明
- 替换初始化逻辑:不再依赖
useState的初始化函数读取数据,改为通过useEffect主动触发读取操作。 - 监听日期id变化:当显示的日期切换时,自动重新读取对应日期的待办数据。
- 跨标签页同步:添加
storage事件监听,确保其他标签页修改待办数据时,当前页面日历能同步更新。 - 清理资源:组件卸载时移除事件监听,避免内存泄漏。
修改后,从TodoList页面返回日历或切换日期时,Day组件都会重新读取localStorage的最新数据,正确显示待办事项总数。
内容的提问来源于stack exchange,提问作者twelve.steven
相关产品推荐
相关产品推荐

