How to Build a Simple Todo App with Hooks

Published on 1.8.2019

React Hooks have revolutionized how we write components in React. Gone are the days of class components for managing state and side effects — now we can achieve all of that and more with the power of hooks like useState and useEffect. In this tutorial, we’ll walk through how to build a simple Todo app using React Hooks, and we’ll add local storage support so that the tasks persist across page reloads.

By the end of this tutorial, you’ll understand how to:

  • Use the useState hook to manage state in your app.
  • Use the useEffect hook to sync your state with local storage.
  • Build a simple CRUD (Create, Read, Update, Delete) app with React.

Let’s get started!


Step 1: Setting Up the Project

To begin, create a new React project (if you don’t already have one):

npx create-react-app todo-hooks
cd todo-hooks
npm start

Now, you’re ready to start building your Todo app!


Step 2: Creating the Todo App Structure

In the src folder, open the App.js file and start by building the basic layout for the app. We’ll create a list of todos, an input field to add new todos, and buttons to handle completing and deleting tasks.

App.js

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

const App = () => {
  // State for storing the todo items
  const [todos, setTodos] = useState([]);
  // State for the current input value
  const [input, setInput] = useState('');

  // useEffect to load todos from localStorage
  useEffect(() => {
    const savedTodos = JSON.parse(localStorage.getItem('todos'));
    if (savedTodos) {
      setTodos(savedTodos);
    }
  }, []);

  // useEffect to save todos to localStorage
  useEffect(() => {
    if (todos.length) {
      localStorage.setItem('todos', JSON.stringify(todos));
    }
  }, [todos]);

  // Function to add a new todo
  const addTodo = (e) => {
    e.preventDefault();
    if (!input.trim()) return; // Prevent adding empty todos
    setTodos([...todos, { text: input, completed: false }]);
    setInput(''); // Clear the input after adding the todo
  };

  // Function to toggle the completion status of a todo
  const toggleComplete = (index) => {
    const updatedTodos = [...todos];
    updatedTodos[index].completed = !updatedTodos[index].completed;
    setTodos(updatedTodos);
  };

  // Function to delete a todo
  const deleteTodo = (index) => {
    const updatedTodos = todos.filter((_, i) => i !== index);
    setTodos(updatedTodos);
  };

  return (
    <div className="App">
      <h1>Todo App with Hooks</h1>
      <form onSubmit={addTodo}>
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add a new task"
        />
        <button type="submit">Add Todo</button>
      </form>

      <ul>
        {todos.map((todo, index) => (
          <li key={index} className={todo.completed ? 'completed' : ''}>
            <span onClick={() => toggleComplete(index)}>{todo.text}</span>
            <button onClick={() => deleteTodo(index)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;

Explanation:

  1. State Management with useState:
    • todos: An array to hold the todo items.
    • input: A string for the value of the input field.
  2. Using useEffect for Local Storage:
    • The first useEffect loads the todos from localStorage when the component mounts (this only runs once).
    • The second useEffect updates localStorage whenever the todos state changes.
  3. Handling Todo Operations:
    • addTodo: Adds a new todo to the todos array. It also clears the input field after the todo is added.
    • toggleComplete: Toggles the completed property of a todo, changing its style when clicked.
    • deleteTodo: Removes a todo from the list by filtering out the item at the specified index.

Step 3: Styling the Todo List

You can add some basic styles in App.css to make the app look a bit more polished.

App.css

.App {
  font-family: Arial, sans-serif;
  width: 300px;
  margin: 0 auto;
  padding: 20px;
  background-color: #f4f4f4;
  border-radius: 8px;
}

input {
  padding: 10px;
  margin-right: 10px;
  width: 200px;
  border-radius: 4px;
  border: 1px solid #ccc;
}

button {
  padding: 10px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #45a049;
}

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

li {
  padding: 10px;
  margin-bottom: 8px;
  background-color: #fff;
  border: 1px solid #ddd;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-radius: 4px;
}

.completed {
  text-decoration: line-through;
  color: #bbb;
}

span {
  cursor: pointer;
}

li button {
  background-color: #f44336;
  color: white;
  border-radius: 4px;
  padding: 5px 10px;
  cursor: pointer;
}

li button:hover {
  background-color: #e53935;
}

Explanation of Styles:

  • input and button: Styled to make the input field and buttons look nicer.
  • li: Each todo item is displayed in a flex container with a delete button.
  • completed: Todos that are marked as completed get a line-through style and a lighter color.

Step 4: Testing the App

At this point, your app is functional, and you can try adding, completing, and deleting todos. The todos will persist even after refreshing the page, thanks to localStorage.

How It Works:

  • When the page loads, the todos are fetched from localStorage and displayed.
  • When a new todo is added, it’s saved in both the state and localStorage.
  • When a todo is toggled as completed or deleted, the state updates and so does localStorage.

Conclusion

You’ve just built a simple Todo app using React Hooks, leveraging useState for state management and useEffect for side effects like syncing with local storage. This pattern can be applied to more complex CRUD applications, and you can easily extend this app by adding features like:

  • Editing todos
  • Sorting or filtering todos
  • Adding due dates or priorities

This approach using React Hooks provides a clean, easy-to-understand way of managing state and side effects in your applications.

Happy coding!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *