You are currently viewing Building a Todo App with React: A Step-by-Step Guide

Building a Todo App with React: A Step-by-Step Guide

React is a popular JavaScript library for building dynamic user interfaces. It allows developers to create reusable components and manage the state of their applications efficiently. One of the common projects to get started with React is building a Todo app. A Todo app helps users keep track of tasks, allowing them to add, delete, and mark tasks as complete.

In this step-by-step guide, we will build a Todo app using React. We will start by setting up a new React project and creating the necessary components. We will manage the state of our application using React’s useState hook and add functionality to add, delete, and toggle todos. Additionally, we will style our app and add local storage to persist todos across sessions.

Setting Up the React Project

Creating a New React App

To get started, we need to create a new React app using Create React App, which sets up a new React project with a standard structure and configuration.

Run the following command to create a new React app:

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

This command sets up a new React application in a directory named todo-app.

Installing Required Packages

For our Todo app, we will use some basic React packages. At this point, we don’t need any additional packages beyond what Create React App provides.

Creating the Todo Components

Creating the Todo List Component

First, we will create a component that displays the list of todos. Create a components directory in the src folder, and then create a file named TodoList.js.

// src/components/TodoList.js
import React from 'react';
import TodoItem from './TodoItem';

const TodoList = ({ todos, toggleTodo, deleteTodo }) => {

  return (
    <ul>

      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
      ))}

    </ul>
  );

};

export default TodoList;

In this component, we map over the list of todos and render a TodoItem component for each todo. We pass the toggleTodo and deleteTodo functions as props to handle interactions with each todo.

Creating the Todo Item Component

Next, we create a component that represents a single todo item. Create a file named TodoItem.js in the components directory.

// src/components/TodoItem.js
import React from 'react';

const TodoItem = ({ todo, toggleTodo, deleteTodo }) => {

  return (
    <li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>

      <span onClick={() => toggleTodo(todo.id)}>{todo.text}</span>
      <button onClick={() => deleteTodo(todo.id)}>Delete</button>

    </li>
  );

};

export default TodoItem;

In this component, we display the text of the todo and apply a line-through style if the todo is completed. We also add buttons to toggle the completion status and delete the todo.

Creating the Add Todo Component

We need a component to add new todos. Create a file named AddTodo.js in the components directory.

// src/components/AddTodo.js
import React, { useState } from 'react';

const AddTodo = ({ addTodo }) => {

  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();

    if (text.trim()) {
      addTodo(text);
      setText('');
    }

  };

  return (
    <form onSubmit={handleSubmit}>

      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new todo"
      />

      <button type="submit">Add</button>

    </form>
  );

};

export default AddTodo;

In this component, we manage the input state using useState. When the form is submitted, we call the addTodo function passed as a prop and reset the input field.

Managing State in the Todo App

Using useState for State Management

Now that we have our components, we need to manage the state of our todos in the main App component. Open src/App.js and update it as follows:

// src/App.js
import React, { useState, useEffect } from 'react';
import TodoList from './components/TodoList';
import AddTodo from './components/AddTodo';

const App = () => {

  const [todos, setTodos] = useState([]);

  const addTodo = (text) => {
    const newTodo = { id: Date.now(), text, completed: false };
    setTodos([...todos, newTodo]);
  };

  const toggleTodo = (id) => {

    setTodos(
      todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );

  };

  const deleteTodo = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id));
  };

  return (
    <div>
      <h1>Todo App</h1>

      <AddTodo addTodo={addTodo} />
      <TodoList todos={todos} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />

    </div>
  );

};

export default App;

In this code, we define the todos state using useState. We also define the addTodo, toggleTodo, and deleteTodo functions to manage the state of our todos. These functions are passed as props to the respective components.

Styling the Todo App

Basic CSS Styling

To style our Todo app, create a styles.css file in the src directory and add some basic styles:

/* src/styles.css */
body {
  font-family: Arial, sans-serif;
}

h1 {
  text-align: center;
}

form {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
}

input {
  padding: 10px;
  font-size: 16px;
  margin-right: 10px;
}

button {
  padding: 10px;
  font-size: 16px;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: flex;
  justify-content: space-between;
  padding: 10px;
  background: #f9f9f9;
  border-bottom: 1px solid #ddd;
  margin-bottom: 10px;
}

li span {
  cursor: pointer;
}

li button {
  background: #ff6347;
  border: none;
  padding: 5px 10px;
  color: white;
  cursor: pointer;
}

Import the CSS file in src/index.js:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './styles.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

These styles provide a basic layout and design for our Todo app, making it more visually appealing.

Conditional Styling for Completed Todos

We have already added conditional styling for completed todos in the TodoItem component by applying a line-through style when a todo is completed.

// src/components/TodoItem.js
<li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
  <span onClick={() => toggleTodo(todo.id)}>{todo.text}</span>
  <button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>

This ensures that completed todos are visually distinct from incomplete ones.

Adding Local Storage

Persisting Todos in Local Storage

To persist the todos in local storage, we need to save the todos state whenever it changes and load the todos from local storage when the component mounts.

Update the App component:

// src/App.js
import React, { useState, useEffect } from 'react';
import TodoList from './components/TodoList';
import AddTodo from './components/AddTodo';

const App = () => {

    const [todos, setTodos] = useState(() => {
        const savedTodos = localStorage.getItem('todos');
        return savedTodos ? JSON.parse(savedTodos) : [];
    });

    useEffect(() => {
        localStorage.setItem('todos', JSON.stringify(todos));
    }, [todos]);

    const addTodo = (text) => {
        const newTodo = { id: Date.now(), text, completed: false };
        setTodos([...todos, newTodo]);
    };

    const toggleTodo = (id) => {
        setTodos(
            todos.map((todo) =>
                todo.id === id ? { ...todo, completed: !todo.completed } : todo
            )
        );
    };

    const deleteTodo = (id) => {
        setTodos(todos.filter((todo) => todo.id !== id));
    };

    return (
        <div>
            <h1>Todo App</h1>
            <AddTodo addTodo={addTodo} />
            <TodoList todos={todos} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
        </div>
    );

};

export default App;

In this code, we initialize the todos state with the saved todos from local storage (if any) using the useState hook. We also use the useEffect hook to save the todos to local storage whenever they change.

Conclusion

Building a Todo app with React is a great way to learn the fundamentals of React development. By creating reusable components, managing state, and adding functionality, you can build a fully functional Todo app. We covered setting up the project, creating components, managing state, styling the app, and persisting data in local storage. These concepts provide a solid foundation for building more complex applications with React.

Additional Resources

To further your understanding of building Todo apps with React, here are some valuable resources:

  1. React Documentation: The official React documentation provides comprehensive information on React features and best practices. React Documentation
  2. React Hooks Documentation: Learn more about React hooks, including useState and useEffect. React Hooks Documentation
  3. CSS-Tricks: A great resource for learning CSS and styling techniques. CSS-Tricks
  4. MDN Web Docs: Comprehensive documentation on web technologies, including JavaScript and DOM manipulation. MDN Web Docs
  5. Udemy: Udemy offers extensive courses on React development for all levels. Udemy React Courses

By leveraging these resources, you can deepen your understanding of React development and enhance your web development skills.

Leave a Reply