Skip to main content

Todo list app

Build a todo list app using react js and localstorage

In This tutorial you will learn how to make a simple todo list app using react js.

You need to follow these steps:

1. Project Setup

  • Open your terminal and navigate to your desired project directory.
  • Run the following command to create a new React app named "todo-list":
npx create-react-app todo-list
  • Navigate to the project directory:
cd todo-list
  • Start the development server to see your initial app running in the browser:
npm start

by default on localhost:3000 you will see a welcome page.

React App

Now let's move on the next step:

2. Working Directory

We'll work in our main component:

  • App.js: The main container component.

Our folder structures would be something like this The typical folder structure for a React to-do list app would look like this:

todo-list/
├── public/ // (Optional) Stores static assets like favicon
│ └── index.html // (Optional) Serves as the entry point for the app
├── src/
│ ├── App.js // Main container component
│ └── index.js // Entry point for the React app
└── package.json // Manages project dependencies

3. Open App.js

Open App.js and replace its content with the following code:

import React from 'react';

function App() {

return (
<div>
<h1>Todo List</h1>
<input
type="text"
placeholder="Enter a todo..."
/>
<button>Add Todo</button>
<ul>
<li>Todos will go here...</li>
</ul>
</div>
);
}

export default App;
  • We Have created a very simple ui for taking input from user
  • We will use Add Todo button to add our todos inside li element.

4. Add todo functionality

In App.js and add the following code:

import React, { useState } from 'react';

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

const handleAddTodo = () => {
if (inputValue.trim() !== '') {
setTodos([...todos, { title: inputValue, isCompleted: false }]);
setInputValue('');
}
};

const handleInputChange = (event) => {
setInputValue(event.target.value);
};

return (
<div>
<h1>Todo List</h1>
<input
value={inputValue}
onChange={handleInputChange}
type="text"
placeholder="Enter a todo..."
/>
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo.title}</li>
))}
</ul>
</div>
);
}

export default App;

  • First We Start by importing useState which will help us to manage our state.
  • Then we use array destructuring to manage our todo which syntax is like this
    const [todos, setTodos] = useState([]);
    where [] means our todos are in an array
const [todos, setTodos] = useState([]);
  • Same thing we do for inputValue, it will handle value from our input element.
const [inputValue, setInputValue] = useState('');
  • Then we create handleInputChange function which monitors input element and set its changed value to inputValue using settter function setInputValue.
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
  • handleAddTodo function first check if inputValue is empty or not, if not then it uses setter function setTodos to update todos value. It then uses array destructuring to keep previous todo there ...todos and adding title to inputValue.

We also have isCompleted value which we will make use later.

const handleAddTodo = () => {
if (inputValue.trim() !== '') {
setTodos([...todos, { title: inputValue, isCompleted: false }]);
setInputValue('');
}
};

5. Adding Complete functionality

Change the code inside your ul element with following one;

<ul>
{todos.map((todo, index) => (
<li key={index}>
<input
type='checkbox'
checked={todo.isCompleted}
onChange={() => handleToggleTodoComplete(index)}
/>
{todo.title}
<button onClick={() => handleDeleteTodo(index)}>Delete</button>
</li>
))}
</ul>
  • Now we are adding functionality to check whether our task completed or not.
  • For this we add an input element which type is checkbox, and it is refering to isCompleted, which is a boolean value.
  • To handle this check functionality we create handleToggleTodoComplete which takes index as an argument every time we change checkbox value. Make handleToggleTodoComplete like this.
const handleToggleTodoComplete = (index) => {
const updatedTodos = todos.map((todo, i) => {
if (i === index) {
return { ...todo, isCompleted: !todo.isCompleted };
}
return todo;
});
setTodos(updatedTodos);
};

It maps over todos set isCompleted to true in the todo that has index which we passed on onChange.

6. Adding Delete functionality

Replace your code inside li element with following code ;


<ul>
{todos.map((todo, index) => (
<li key={index}>
<input
type='checkbox'
checked={todo.isCompleted}
onChange={() => handleToggleTodoComplete(index)}
/>
{todo.title}
<button onClick={() => handleDeleteTodo(index)}>Delete</button>
</li>
))}
</ul>
  • We have added a button element which listens for onClick event and call handleDeleteTodo function with index value.
  • Now Make handleDeleteTodo function and add following code to that
const handleDeleteTodo = (index) => {
const updatedTodos = todos.filter((_, i) => i !== index);
setTodos(updatedTodos);
};

It uses javascript filter array method to remove that perticular todo which has matching index.

7. Add localStorage functionality

Now we are able to do add delete and other operations in our application but ones you refresh the page you will see all your todo has gone.

To fix this we will add localStorage which will save our todos in browsers storage

Update App.js below code :

import React, { useState, useEffect } from 'react';

function TodoApp() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');

// Load todos from local storage on component mount
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem('todos'));
if (storedTodos) {
setTodos(storedTodos);
}
}, []);

// Update local storage whenever todos change
useEffect(() => {
if (todos.length > 0) localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

const handleInputChange = (event) => {
setInputValue(event.target.value);
};

const handleAddTodo = () => {
if (inputValue.trim() !== '') {
setTodos([...todos, { title: inputValue, isCompleted: false }]);
setInputValue('');
}
};

const handleDeleteTodo = (index) => {
const updatedTodos = todos.filter((_, i) => i !== index);
setTodos(updatedTodos);

// Check if there are no todos left
if (updatedTodos.length === 0) {
// Clear local storage
localStorage.removeItem('todos');
}
};

const handleToggleTodoComplete = (index) => {
const updatedTodos = todos.map((todo, i) => {
if (i === index) {
return { ...todo, isCompleted: !todo.isCompleted };
}
return todo;
});
setTodos(updatedTodos);
};

return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={inputValue}
onChange={handleInputChange}`
placeholder="Enter a todo..."
/>
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
<input
type='checkbox'
checked={todo.isCompleted}
onChange={() => handleToggleTodoComplete(ind~ex)}
/>
{todo.title}
<button onClick={() => handleDeleteTodo(index)}>Delete</button>
</li>
))}
</ul>
</div>
);
}

export default TodoApp;

  • We have imported useEffect which will allow us to get and save our todos everytime they change and when we load the page.
  • First we add our todo in localStorage like this
useEffect(() => {
if (todos.length > 0) localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

We do this every time todos change. useEffect will run everytime the value inside [] changes. That's why we have added todos inside [].

We are also checking todos length it will help use when we are loading the page.

  • Then add another useEffect which will run only ones the page load
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem('todos'));
if (storedTodos) {
setTodos(storedTodos);
}
}, []);

We are also using json to save todos in localStorage, Which converts into javascript objest seemlessly.

  • Also when deleting last item we remove todos form localstorage completly by adding following code to handleDeleteTodo function.
if (updatedTodos.length === 0) {
localStorage.removeItem('todos');
}
Challenge for you

Now that you've created your own todo-list app it is not end yet you can improve it by using your own css and by adding other functionlities like updating todo and database integration