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
tasks
fromApp
toDashboard
: TheApp
component should passtasks
toDashboard
to avoid errors related to undefinedprops.tasks
inDashboard
. - Adjusting Route Props: Since
Dashboard
needstasks
fromApp
, we should ensuretasks
is 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:
DATA
is declared inApp
and passed toDashboard
via 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:
initialTasks
is renamed when destructuringprops
to clarify it as the initial state oftasks
.- Each function (
addTask
,toggleTaskCompleted
,deleteTask
, andeditTask
) correctly updatestasks
using 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.