I’m encountering an uncaught runtime error in my React To Do list application after implementing a login feature. The app compiles without issues, but when I click the login button, the error shows up in the browser console. I’ve reviewed my code, but I can’t identify what’s causing it. I suspect it might be related to the useToken hook or the way the Dashboard component is being rendered. Additionally, I noticed a potential syntax issue with string interpolation in the Dashboard component where id: todo-${nanoid()} might be causing an issue, and the tasks prop might not be passed correctly.
Error Code:
codeimport 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;
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')
);
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>
);
}To correct and optimize this code, there are a few issues to address:
- Passing
tasksfromApptoDashboard: TheAppcomponent should passtaskstoDashboardto avoid errors related to undefinedprops.tasksinDashboard. - Adjusting Route Props: Since
DashboardneedstasksfromApp, we should ensuretasksis properly passed down.
Solution and Corrected Code:
Here’s the updated code with comments explaining each fix.
App Component (App.js)
In the App component, modify the way tasks is passed to Dashboard.
code// 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';
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 }
];
function App() {
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={DATA} />} />
</Routes>
</div>
);
}
export default App;
In this version:
DATAis declared inAppand passed toDashboardvia props.
Index File (index.js)
The index.js file renders the App component within the BrowserRouter.
code// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
Dashboard Component (Dashboard.js)
In the Dashboard component, tasks are received from props, and state management is handled for task additions, deletions, and updates.
code// 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({ tasks: initialTasks }) {
const [tasks, setTasks] = useState(initialTasks);
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) =>
id === task.id ? { ...task, completed: !task.completed } : 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) =>
id === task.id ? { ...task, name: newName } : 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>To-Do 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>
);
}
In this updated Dashboard component:
initialTasksis renamed when destructuringpropsto clarify it as the initial state oftasks.- Each function (
addTask,toggleTaskCompleted,deleteTask, andeditTask) correctly updatestasksusing state methods.
Final Thoughts
This code now properly passes the initial tasks data from App to Dashboard, handles route rendering correctly, and maintains clear component structures for easy state and task management.
