Create a Todo List Application in React (add, delete, and edit items)

Last updated: December 23, 2023.

In this tutorial, we will create a todo list application in React that initially displays an array to todos as string values.

Then we will look at how to add, delete and edit todos.

Display todo list items

Todo list items can be displayed in a list by mapping over them using the map() function.

So that the UI will update whenever todos are changed later, the todos item are stored as a state value:

import { useState } from "react";

const App = () => {

  const initialTodos = ["Code", "Sleep", "Eat"];

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

  return (
    <div>
      <ul>
        { 
          todos.map((todo, index) => {
            return <li key={index}>{ todo }</li>
          }) 
        }
      </ul>
    </div>
  );

};

export default App;

Adding todos

To add todos to the list, you can add an <input> element that, when a user types, update the state value of todoInput using setTodoInput.

When the <button> below it is clicked, this triggers an updating of todos using setTodos, the new value of which is the previous array plus the new string value of todoInput:

import { useState } from "react";

const App = () => {

  const initialTodos = ["Code", "Sleep", "Eat"];

  const [ todos, setTodos ] = useState(initialTodos);
  const [ todoInput, setTodoInput ] = useState('');

  return (
    <div>
      <ul>
        { 
          todos.map((todo, index) => {
            return <li key={index}>{ todo }</li>
          }) 
        }
      </ul>
      <div>
        <input 
          type="text" 
          onChange={ (e) => { setTodoInput(e.target.value) } } 
          value={todoInput}
        />
        <button onClick={ () => { setTodos(prev => [...prev, todoInput]) } }>Add todo</button>
      </div>
    </div>
  );

};

export default App;

Deleting todos

To be able to delete a todo item, a button that displays "Delete" can be added next to each todo item.

When it is clicked, the new todo items are set to a filtered version of the current items minus the todo item that matches the value of index:

import { useState } from "react";

const App = () => {

  const initialTodos = ["Code", "Sleep", "Eat"];

  const [ todos, setTodos ] = useState(initialTodos);
  const [ todoInput, setTodoInput ] = useState('');

  return (
    <div>
      <ul>
        { 
          todos.map((todo, index) => {
            return(
              <li key={index}>
                { todo }
                <button onClick={ () => { setTodos(prev => prev.filter((todo, i) => i !== index)) } }>Delete</button>
              </li>
            )
          }) 
        }
      </ul>
      <div>
        <input 
          type="text" 
          onChange={ (e) => { setTodoInput(e.target.value) } } 
          value={todoInput}
        />
        <button onClick={ () => { setTodos(prev => [...prev, todoInput]) } }>Add todo</button>
      </div>
    </div>
  );

};

export default App;

Editing todos

To edit todo items, we can create a <button> that display "Edit" next to each todo item.

Clicking this will cause the state value editingTodo to update to the index of the item clicked, which leads to conditional rendering of the item’s current value in an <input> element. Its current value is set as the state value editingTodoInput which is also set when the "Edit" button is clicked.

Then, by clicking the <button> displaying "Done editing" next to the <input> that is visible when editing, todos is updated with the new value set for the edited item.

Finally, the value of editingTodo is set to false and this closes the editing view.

import { useState } from "react";

const App = () => {

  const initialTodos = ["Code", "Sleep", "Eat"];

  const [ todos, setTodos ] = useState(initialTodos);
  const [ todoInput, setTodoInput ] = useState('');
  const [ editingTodo, setEditingTodo ] = useState(false);
  const [ editingTodoInput, setEditingTodoInput ] = useState(null);

  return (
    <div>
      <ul>
        { 
          todos.map((todo, index) => {
            return(
              <li key={index}>
                { (editingTodo === index) ? 
                  <>
                    <input value={ editingTodoInput } onChange={ (e) => { setEditingTodoInput(e.target.value) } }/>
                    <button 
                      onClick={ () => { setTodos(prev => { prev[index] = editingTodoInput; return prev; }); setEditingTodo(false) } }
                    >
                      Done editing
                    </button>
                  </>
                  : todo 
                }
                <button onClick={ () => { setEditingTodoInput(todos[index]); setEditingTodo(index); } }>Edit</button>
                <button onClick={ () => { setTodos(prev => prev.filter((todo, i) => i !== index)) } }>Delete</button>
              </li>
            )
          }) 
        }
      </ul>
      <div>
        <input 
          type="text" 
          onChange={ (e) => { setTodoInput(e.target.value) } } 
          value={todoInput}
        />
        <button onClick={ () => { setTodos(prev => [...prev, todoInput]) } }>Add todo</button>
      </div>
    </div>
  );

};

export default App;