We revamped our site to better serve our users!
Frontend Hire
Todo App with React, TypeScript, and TDD

Adding Tasks

In this section, we'll add the ability to add tasks to the list.

Let us add interactivity by letting the user add tasks to the list. We'll add a text input and a button. When the user clicks the button, we'll add the text from the input to the list of tasks.

Adding a Text Input and Button

First, add a text input and a button to the page. We'll add them just above the list of tasks.

./src/App.tsx
// Rest of the code omitted for brevity
 
<div>
  <h1>Tasks</h1>
  <input />
  <button>Add</button>
  <ul>
    {tasks.map((task) => (
      <li key={task.id}>{task.title}</li>
    ))}
  </ul>
</div>
 
// Rest of the code omitted for brevity

Though this is good enough for now, we can and should make it more accessible by adding a label for the input and a htmlFor attribute on the label that matches the id of the input.

./src/App.tsx
// Rest of the code omitted for brevity
 
<div>
  <h1>Tasks</h1>
  <label htmlFor="task-input">Add Task: </label>
  <input id="task-input" />
  <button>Add</button>
  <ul>
    {tasks.map((task) => (
      <li key={task.id}>{task.title}</li>
    ))}
  </ul>
</div>
 
// Rest of the code omitted for brevity

Now, clicking on the label will focus on the input.

Adding a Task

The Add button doesn't do anything yet. Let's add a click handler to it that will log to the console for now.

./src/App.tsx
// Rest of the code omitted for brevity
 
<div>
  <h1>Tasks</h1>
  <label htmlFor="task-input">Add Task: </label>
  <input id="task-input" />
  <button
    onClick={() => {
      console.log('Add');
    }}
  >
    Add
  </button>
  <ul>
    {tasks.map((task) => (
      <li key={task.id}>{task.title}</li>
    ))}
  </ul>
</div>
 
// Rest of the code omitted for brevity

Let's capture the value of the input and log it to the console. One of the ways to do this in React is to use a state variable to track the value of the input.

We'll use the useState hook to create a state variable and a setter function. We'll also add a value attribute to the input and set it to the state variable. While we are at it, let's add an onChange handler to the input to update the state variable. This input is now a controlled input.

./src/App.tsx
import React from 'react'; 
 
// Some of the code omitted for brevity
 
function App() {
  // Some of the code omitted for brevity
 
  const [taskName, setTaskName] = React.useState(''); 
 
  return (
    <div>
      {/* Some of the code omitted for brevity */}
 
      <input
        id="task-input"
        value={taskName} 
        onChange={(e) => setTaskName(e.target.value)} 
      />
      {/* Some of the code omitted for brevity */}
    </div>
  );
}
 
// Rest of the code omitted for brevity

Now, we can update the button click handler to log the value of the input.

./src/App.tsx
// Rest of the code omitted for brevity
 
<button
  onClick={() => {
    console.log(taskName); 
  }}
>
  Add
</button>
 
// Rest of the code omitted for brevity

Before we can even add tasks to the list, we have to update our current tasks variable that holds our list of tasks to use state variables; otherwise, the list will not update when we add a task.

./src/App.tsx
// Rest of the code omitted for brevity
 
function App() {
  const [tasks, setTasks] = React.useState<Task[]>([
    {
      id: 1,
      title: 'Learn React',
      isCompleted: true,
      priority: 'p1',
    },
  ]);
 
  const [taskName, setTaskName] = React.useState('');
 
  // Rest of the code omitted for brevity
}

We can add a task to the list by updating the tasks state variable. We'll update the state variable using the setTasks setter function. We'll use the spread operator to create a new array with the new task added.

./src/App.tsx
// Rest of the code omitted for brevity
 
<button
  onClick={() => {
    setTasks([
      ...tasks,
      {
        id: new Date().getTime(), // Not a great way to generate IDs
        title: taskName,
        isCompleted: false,
      },
    ]);
  }}
>
  Add
</button>
 
// Rest of the code omitted for brevity

As discussed in the previous section, unique IDs are usually created by a database. In this project, we'll use the current time in milliseconds as the ID for each task.

Great, we are now able to add tasks to the list!

Our JSX code is getting a bit long, so let's separate the button's onClick handler into its own function. The input's onChange handler is acceptable for now.

./src/App.tsx
// Rest of the code omitted for brevity
 
function App() {
  // Some of the code omitted for brevity
 
  const [taskName, setTaskName] = React.useState('');
 
  const onAddTask = () => {
    setTasks([
      ...tasks,
      {
        id: new Date().getTime(), // Not a great way to generate IDs
        title: taskName,
        isCompleted: false,
      },
    ]);
  };
 
  return (
    <div>
      {/* Some of the code omitted for brevity */}
      <button onClick={onAddTask}>Add</button>
      {/* Some of the code omitted for brevity */}
    </div>
  );
}
 
export default App;

Awesome, but there are a ton of things that can go wrong when a user adds a task. For example, the user can add an empty task.

This gives us a good opportunity to learn about Testing. We'll cover testing in the subsequent sections.

At this point, your code should be a good match to the branch of the repository: 3-adding-tasks

On this page