Fix Handle Runtime Errors in React Applications

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:

  1. Passing tasks from App to Dashboard: The App component should pass tasks to Dashboard to avoid errors related to undefined props.tasks in Dashboard.
  2. Adjusting Route Props: Since Dashboard needs tasks from App, we should ensure tasks 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 in App and passed to Dashboard 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 destructuring props to clarify it as the initial state of tasks.
  • Each function (addTask, toggleTaskCompleted, deleteTask, and editTask) correctly updates tasks 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.

Related blog posts