Build a Simple React JavaScript Interactive To-Do List App: A Beginner’s Guide

Tired of scattered sticky notes and forgotten tasks? In today’s fast-paced world, staying organized is crucial. A well-designed to-do list app can be your digital personal assistant, helping you manage your daily, weekly, or even monthly responsibilities. This tutorial will guide you, step-by-step, through building a simple, yet functional, to-do list application using React.js. We’ll cover everything from setting up your project to adding features like adding, deleting, and marking tasks as complete. By the end, you’ll have a practical project to showcase your React skills and a valuable tool to boost your productivity.

Why Build a To-Do List App?

Creating a to-do list app is an excellent learning experience for React developers of all levels. It allows you to:

  • Master React Fundamentals: You’ll work with components, state management, event handling, and conditional rendering – core concepts in React.
  • Build a Practical Application: You’ll create something useful that you can use daily and share with others.
  • Enhance Your Portfolio: A working to-do list app is a great addition to your portfolio, demonstrating your ability to build interactive web applications.
  • Learn by Doing: There’s no better way to learn than by building a real-world project. You’ll encounter challenges and learn how to solve them.

What We’ll Cover

In this tutorial, we will:

  • Set up a React project using Create React App.
  • Create the necessary components: App, ToDoForm, and ToDoItem.
  • Manage the state of your to-do list.
  • Implement functionality to add, delete, and mark tasks as complete.
  • Style the app with basic CSS.
  • Handle user input and events.

Prerequisites

Before you start, make sure you have the following:

  • Node.js and npm (or yarn) installed: This is required to run the React development environment.
  • Basic knowledge of HTML, CSS, and JavaScript: Familiarity with these languages will help you understand the code.
  • A code editor: Visual Studio Code, Sublime Text, or any editor you prefer.

Step-by-Step Guide

1. Setting Up the Project

Let’s start by creating a new React project using Create React App. Open your terminal and run the following command:

npx create-react-app todo-app
cd todo-app

This command creates a new React project named “todo-app”. Navigate into the project directory using cd todo-app.

2. Project Structure and Initial Setup

Open the project in your code editor. You’ll see a basic project structure created by Create React App. The core files we’ll be working with are:

  • src/App.js: The main component of our application.
  • src/App.css: The stylesheet for our application.
  • src/index.js: The entry point of our application.

Let’s clean up src/App.js and start with a basic structure. Replace the content of src/App.js with the following code:

import React, { useState } from 'react';
import './App.css';

function App() {
  const [todos, setTodos] = useState([]);

  return (
    <div className="app">
      <h1>To-Do List</h1>
      <!-- ToDoForm component will go here -->
      <!-- ToDoList component will go here -->
    </div>
  );
}

export default App;

Here, we import React and the stylesheet. We also import the useState hook, which will be used to manage the list of to-do items. We’ve initialized an empty array called todos using the useState hook. The basic structure includes a title and placeholders for our components.

3. Creating the ToDoForm Component

The ToDoForm component will handle user input for adding new tasks. Create a new file named ToDoForm.js inside the src directory and add the following code:

import React, { useState } from 'react';

function ToDoForm({ addTodo }) {
  const [value, setValue] = useState('');

  const handleSubmit = e => {
    e.preventDefault();
    if (!value) return;
    addTodo(value);
    setValue('');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        className="input"
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Add ToDo..." />
    </form>
  );
}

export default ToDoForm;

This component uses the useState hook to manage the input field’s value. The handleSubmit function prevents the default form submission behavior, calls the addTodo function (passed as a prop from the App component), and resets the input field. The addTodo function will be defined in App.js later. We also pass the addTodo function as a prop to this component.

Now, import and use the ToDoForm component in App.js. Modify App.js as follows:

import React, { useState } from 'react';
import './App.css';
import ToDoForm from './ToDoForm';

function App() {
  const [todos, setTodos] = useState([]);

  const addTodo = text => {
    const newTodos = [...todos, { text, isCompleted: false }];
    setTodos(newTodos);
  };

  return (
    <div className="app">
      <h1>To-Do List</h1>
      <ToDoForm addTodo={addTodo} />
      <!-- ToDoList component will go here -->
    </div>
  );
}

export default App;

We’ve imported the ToDoForm component and passed the addTodo function as a prop. The addTodo function takes the text from the form and adds a new todo object to the todos state, which includes the text and sets isCompleted to false.

4. Creating the ToDoItem Component

The ToDoItem component will display each individual to-do item. Create a new file named ToDoItem.js inside the src directory and add the following code:

import React from 'react';

function ToDoItem({ todo, index, completeTodo, removeTodo }) {
  return (
    <div
      className="todo"
      style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
    >
      {todo.text}
      <div>
        <button onClick={() => completeTodo(index)}>Complete</button>
        <button onClick={() => removeTodo(index)}>x</button>
      </div>
    </div>
  );
}

export default ToDoItem;

This component receives a todo object, the index of the todo item, and functions for completing and removing the todo. It displays the todo text and provides buttons to complete or remove the task. The style property uses a ternary operator to conditionally apply a line-through style if the task is completed.

5. Creating the ToDoList Component

The ToDoList component will render the list of to-do items, using the ToDoItem component. Create a new file named ToDoList.js inside the src directory and add the following code:

import React from 'react';
import ToDoItem from './ToDoItem';

function ToDoList({ todos, completeTodo, removeTodo }) {
  return (
    <div>
      {todos.map((todo, index) => (
        <ToDoItem
          key={index}
          index={index}
          todo={todo}
          completeTodo={completeTodo}
          removeTodo={removeTodo}
        />
      ))}
    </div>
  );
}

export default ToDoList;

This component receives the todos array, and the functions completeTodo and removeTodo as props. It maps over the todos array and renders a ToDoItem for each todo object, passing the required props. The key prop is important for React to efficiently update the list.

Now, we need to import and use ToDoList in App.js. Modify App.js as follows:

import React, { useState } from 'react';
import './App.css';
import ToDoForm from './ToDoForm';
import ToDoList from './ToDoList';

function App() {
  const [todos, setTodos] = useState([]);

  const addTodo = text => {
    const newTodos = [...todos, { text, isCompleted: false }];
    setTodos(newTodos);
  };

  const completeTodo = index => {
    const newTodos = [...todos];
    newTodos[index].isCompleted = !newTodos[index].isCompleted;
    setTodos(newTodos);
  };

  const removeTodo = index => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
  };

  return (
    <div className="app">
      <h1>To-Do List</h1>
      <ToDoForm addTodo={addTodo} />
      <ToDoList
        todos={todos}
        completeTodo={completeTodo}
        removeTodo={removeTodo}
      />
    </div>
  );
}

export default App;

We’ve imported ToDoList and implemented the completeTodo and removeTodo functions. The completeTodo function toggles the isCompleted property of a specific todo item. The removeTodo function removes a todo item from the array using the splice method. We pass the todos array and the functions to the ToDoList component as props.

6. Adding Basic Styling (CSS)

To make the app look better, let’s add some basic styling. Open src/App.css and add the following CSS:


.app {
  text-align: center;
  max-width: 500px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.input {
  padding: 10px;
  margin-right: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 16px;
}

.todo {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  margin-bottom: 5px;
  border: 1px solid #eee;
  border-radius: 5px;
}

.todo button {
  margin-left: 5px;
  padding: 5px 10px;
  border: none;
  border-radius: 3px;
  background-color: #f0f0f0;
  cursor: pointer;
}

.todo button:hover {
  background-color: #ddd;
}

.input {
    width: 70%;
}

This CSS provides basic styling for the app container, input field, to-do items, and buttons. Feel free to customize it to your liking.

7. Running the Application

Now that you’ve completed the code, run your application using the following command in your terminal:

npm start

This will start the development server, and your app should open in your default web browser at http://localhost:3000 (or a different port if 3000 is unavailable).

Common Mistakes and How to Fix Them

1. Incorrect State Updates

Mistake: Directly modifying the state array instead of creating a new array. For example: todos.push({ text: 'new task' }); setTodos(todos);

Fix: Always create a new array or object when updating state to ensure React can detect the changes and re-render the component. Use the spread operator (...) to create a copy of the array and then modify the copy. For example: const newTodos = [...todos, { text: 'new task' }]; setTodos(newTodos);

2. Missing or Incorrect Keys in Lists

Mistake: Not providing a unique key prop when rendering lists of elements using .map().

Fix: Provide a unique key prop for each element in the list. This helps React efficiently update the DOM. The index of the item can be used as a key, but it’s not ideal if the order of the list can change. If you have unique IDs for your items, use those as keys. Example: <ToDoItem key={index} ... />

3. Incorrect Event Handling

Mistake: Not preventing the default form submission behavior. This can cause the page to reload when the form is submitted.

Fix: Use e.preventDefault() inside the event handler function. For example: const handleSubmit = (e) => { e.preventDefault(); ... }

4. Prop Drilling

Mistake: Passing props down through multiple levels of components when a component only needs a prop from a parent component.

Fix: Consider using Context or a state management library like Redux or Zustand for more complex applications to avoid prop drilling.

Key Takeaways

  • Components: React applications are built with reusable components.
  • State Management: The useState hook is fundamental for managing component state.
  • Event Handling: Handling user interactions is essential for building interactive applications.
  • Props: Props are used to pass data from parent to child components.
  • Conditional Rendering: You can conditionally render content based on the state of your application.

FAQ

  1. Can I store the to-do list data locally?

    Yes, you can use localStorage to store the to-do list data in the user’s browser. You would need to retrieve the data from localStorage when the app loads and save the data to localStorage whenever the to-do list changes.

  2. How can I add more features to this app?

    You can add features like due dates, priority levels, categories, and the ability to edit tasks. You could also integrate with a backend server to store the data persistently.

  3. How do I deploy this app?

    You can deploy the app to platforms like Netlify, Vercel, or GitHub Pages. You’ll need to build the app for production using npm run build and then deploy the generated files.

  4. What are some good resources for learning more React?

    The official React documentation is an excellent resource. Other helpful resources include tutorials on websites like freeCodeCamp, MDN Web Docs, and YouTube channels dedicated to React development.

  5. How can I improve the performance of my React app?

    You can optimize performance by using techniques like memoization (using React.memo), code splitting, and lazy loading. Also, be mindful of unnecessary re-renders.

Building a to-do list app is a fantastic starting point for any React developer. You’ve learned the basics of components, state management, and event handling. As you continue to build and experiment, you’ll discover new ways to improve your code and create more complex and engaging applications. The principles you’ve learned here are transferable to a wide variety of React projects. Keep practicing, and you’ll become proficient in React development in no time. Now, go forth and organize your life, one to-do at a time!