I've got a simple To-Do list application written in React, and I'm trying to add login to it. I want to have the login page as the landing page and redirect to the Dashboard after login, but I'm still new to React. The code compiles without errors, and I get the following error in the browser after I submit username and password:
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
I'm not sure how to go about debugging this error. Could this be a routing problem? Note that there isn't any registration or real verification happening. I'm just interesting in getting the login to redirect at the moment.
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>
);
}
The error is "tasks is undefined" somewhere in "Dashboard@http://localhost:3000/static/js/bundle.js:215:20". Based on the code it looks like the tasks
state in Dashboard
is undefined because props.tasks
is undefined because no tasks
prop was passed to Dashboard
.
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={<Dashboard />} // <-- no tasks prop passed!
/>
</Routes>
export default function Dashboard(props) {
const [tasks, setTasks] = useState(props.tasks); // <-- undefined
...
Anywhere in Dashboard
where tasks
is referenced, e.g. like tasks.map()
or tasks.filter()
, will throw an error because tasks
is undefined.
Pass the tasks
prop passed to App
through to children components.
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} /> // <-- tasks passed here
</BrowserRouter>,
document.getElementById('root')
);
App
function App({ tasks }) { // <-- access tasks prop
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} />} // <-- pass tasks prop
/>
</Routes>
</div>
);
}
Dashboard
export default function Dashboard(props) {
const [tasks, setTasks] = useState(props.tasks); // <-- defined 🙂
...