React待办应用登录跳转Dashboard时出现tasks未定义错误求助
React登录跳转后Dashboard报tasks未定义的问题
我基于React开发了一个简易待办事项应用,正在为其添加登录功能,期望将登录页设为首页,登录完成后跳转至Dashboard页面,但刚接触React。代码编译无报错,但提交用户名和密码后,浏览器出现如下错误:
tasks is undefined Dashboard@http://localhost:3000/static/js/bundle.js:215:20 renderWithHooks@http://localhost:3000/static/js/bundle.js:26189:31 mountIndeterminateComponent@http://localhost:3000/static/js/bundle.js:29473:17 beginWork@http://localhost:3000/static/js/bundle.js:30769:20 callCallback@http://localhost:3000/static/js/bundle.js:15785:18 invokeGuardedCallbackDev@http://localhost:3000/static/js/bundle.js:15829:20 invokeGuardedCallback@http://localhost:3000/static/js/bundle.js:15886:35 beginWork$1@http://localhost:3000/static/js/bundle.js:35750:32 performUnitOfWork@http://localhost:3000/static/js/bundle.js:34998:16 workLoopSync@http://localhost:3000/static/js/bundle.js:34921:26 renderRootSync@http://localhost:3000/static/js/bundle.js:34894:11 performSyncWorkOnRoot@http://localhost:3000/static/js/bundle.js:34586:38 flushSyncCallbacks@http://localhost:3000/static/js/bundle.js:22622:26 flushSyncCallbacksOnlyInLegacyMode@http://localhost:3000/static/js/bundle.js:22604:9 scheduleUpdateOnFiber@http://localhost:3000/static/js/bundle.js:34116:11 dispatchSetState@http://localhost:3000/static/js/bundle.js:27217:32 ./node_modules/react-router-dom/dist/index.js/BrowserRouter/setState<@http://localhost:3000/static/js/bundle.js:39391:101 push@http://localhost:3000/static/js/bundle.js:1815:15 ./node_modules/react-router/dist/index.js/useNavigateUnstable/navigate<@http://localhost:3000/static/js/bundle.js:40519:61 handleSubmit@http://localhost:3000/static/js/bundle.js:841:15
我不确定该如何调试这个错误,这会不会是路由问题?注:目前没有注册或真实验证逻辑,我只想先实现登录跳转功能。
相关代码
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; import App from './App'; const DATA = [ {id: "todo-0", name: "Make bed", completed: true}, {id: "todo-1", name: "Fold laundry", completed: false}, {id: "todo-2", name: "Brush teeth", completed: false} ]; ReactDOM.render( <BrowserRouter> <App tasks={DATA} /> </BrowserRouter>, document.getElementById('root') );
Login.js
import React, { useState } from 'react'; import PropTypes from 'prop-types'; import './Login.css'; import { useNavigate } from 'react-router-dom'; async function loginUser(credentials) { return fetch('http://localhost:8080/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }) .then(data => data.json()) } export default function Login({ setToken }) { const navigate = useNavigate(); const [username, setUserName] = useState(); const [password, setPassword] = useState(); const handleSubmit = async e => { e.preventDefault(); const token = await loginUser({ username, password }); if(token) { setToken(token); navigate("/dashboard"); } } return( <div className="login-wrapper"> <h1>Please Log In</h1> <form onSubmit={handleSubmit}> <label> <p>Username</p> <input type="text" onChange={e => setUserName(e.target.value)} /> </label> <label> <p>Password</p> <input type="password" onChange={e => setPassword(e.target.value)} /> </label> <div> <button type="login">Login</button> </div> </form> </div> ) } Login.propTypes = { setToken: PropTypes.func.isRequired };
App.js
import Dashboard from './components/Dashboard/Dashboard'; import Login from './components/Login/Login'; import { Route, Routes } from "react-router-dom"; import './App.css'; import useToken from './useToken'; function App() { const {token, setToken} = useToken(); if(!token) { return <Login setToken={setToken} /> } return ( <div> <Routes> <Route path="/login" element={<Login />} /> <Route path="/dashboard" element={<Dashboard />} /> </Routes> </div> ); } export default App;
Dashboard.js
import React, { useState, useRef, useEffect } from "react"; import Form from "./Form"; import FilterButton from "./FilterButton"; import Todo from "./Todo"; import { nanoid } from "nanoid"; function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; } const FILTER_MAP = { All: () => true, Active: (task) => !task.completed, Completed: (task) => task.completed }; const FILTER_NAMES = Object.keys(FILTER_MAP); export default function Dashboard(props) { const [tasks, setTasks] = useState(props.tasks); const [filter, setFilter] = useState('All'); function addTask(name) { const newTask = { id: `todo-${nanoid()}`, name, completed: false }; setTasks([...tasks, newTask]); } function toggleTaskCompleted(id) { const updatedTasks = tasks.map((task) => { // if this task has the same ID as the edited task if (id === task.id) { // use object spread to make a new object // whose `completed` prop has been inverted return {...task, completed: !task.completed} } return task; }); setTasks(updatedTasks); } function deleteTask(id) { const remainingTasks = tasks.filter((task) => id !== task.id); setTasks(remainingTasks); } function editTask(id, newName) { const editedTaskList = tasks.map((task) => { // if this task has the same ID as the edited task if (id === task.id) { // return {...task, name: newName} } return task; }); setTasks(editedTaskList); } const taskList = tasks.filter(FILTER_MAP[filter]).map((task) => ( <Todo id={task.id} name={task.name} completed={task.completed} key={task.id} toggleTaskCompleted={toggleTaskCompleted} deleteTask={deleteTask} editTask={editTask}/> )); const filterList = FILTER_NAMES.map((name) => ( <FilterButton key={name} name={name} isPressed={name === filter} setFilter={setFilter} /> )); const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task'; const headingText = `${taskList.length} ${tasksNoun}`; const listHeadingRef = useRef(null); const prevTaskLength = usePrevious(tasks.length); useEffect(() => { if (tasks.length - prevTaskLength === -1) { listHeadingRef.current.focus(); } }, [tasks.length, prevTaskLength]); return( <div className="todoapp stack-large"> <h1>Trevor's ToDo List</h1> <Form addTask={addTask} /> <div className="filters btn-group stack-exception"> {filterList} </div> <h2 id="list-heading" tabIndex="-1" ref={listHeadingRef}> {headingText} </h2> <ul className="todo-list stack-large stack-exception" aria-labelledby="list-heading" > {taskList} </ul> </div> ); }
问题原因及解决办法
核心问题
Dashboard组件依赖props.tasks初始化内部状态,但App.js渲染Dashboard时未传递该props。虽然index.js将DATA传给了App组件,但App没有向下传递给Dashboard,导致props.tasks为undefined,初始化的tasks state也变成undefined,后续执行tasks.filter时抛出错误。
具体修复步骤
- 修改App.js,传递tasks props给Dashboard
让App组件接收tasks props,并在路由中传递给Dashboard:
function App({ tasks }) { const {token, setToken} = useToken(); if(!token) { return <Login setToken={setToken} /> } return ( <div> <Routes> <Route path="/login" element={<Login setToken={setToken} />} /> <Route path="/dashboard" element={<Dashboard tasks={tasks} />} /> </Routes> </div> ); }
- 为Dashboard的props设置默认值(可选但推荐)
给Dashboard的tasks props设置默认空数组,避免后续遗漏传参导致错误:
export default function Dashboard({ tasks = [] }) { const [tasksState, setTasks] = useState(tasks); // 其余代码保持不变 }
- 修复Login组件的提交按钮类型(额外优化)
将Login组件中按钮的type="login"改为type="submit",确保表单能正常触发提交事件:
<button type="submit">Login</button>
完成以上修改后,登录跳转至Dashboard时组件就能获取到tasks数据,不会再出现undefined错误。
内容的提问来源于stack exchange,提问作者CodenameCaptainFuture
相关产品推荐
相关产品推荐

