React Mini Project: Todo App

Combine components, state, events and lists.

✍️ 3 exercises + code challenge in this lesson

This small app uses everything from the tutorial:

  • Components with props
  • useState for the todo list and the input
  • Controlled form with onSubmit
  • Rendering a list with keys
  • Updating and filtering arrays immutably

Try extending it: add an "edit" button, a filter (all / active / done), or save todos in localStorage with useEffect.

Key Takeaways: React Mini Project: Todo App

Mastering React Mini Project: Todo App helps you write clean and reliable React code. Follow standard conventions, test edge cases, and keep logic simple. Try running the code example below and complete the exercises to build confidence.

React Mini Project: Todo App Code Example

React
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.25.6/babel.min.js"></script>

<script type="text/babel">
const { useState, useEffect, useRef, useMemo, createContext, useContext } = React;
function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li style={{ display: "flex", gap: 8, alignItems: "center", padding: "4px 0" }}>
      <input type="checkbox" checked={todo.done} onChange={() => onToggle(todo.id)} />
      <span style={{ flex: 1, textDecoration: todo.done ? "line-through" : "none" }}>{todo.text}</span>
      <button onClick={() => onDelete(todo.id)}>✕</button>
    </li>
  );
}

function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: "Learn JSX", done: true },
    { id: 2, text: "Practise hooks", done: false },
  ]);
  const [text, setText] = useState("");

  const add = (e) => {
    e.preventDefault();
    if (!text.trim()) return;
    setTodos([...todos, { id: Date.now(), text, done: false }]);
    setText("");
  };
  const toggle = (id) => setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
  const remove = (id) => setTodos(todos.filter(t => t.id !== id));
  const left = todos.filter(t => !t.done).length;

  return (
    <div style={{ maxWidth: 360, fontFamily: "sans-serif" }}>
      <h2>My Todos ({left} left)</h2>
      <form onSubmit={add}>
        <input value={text} onChange={e => setText(e.target.value)} placeholder="New todo" />
        <button>Add</button>
      </form>
      <ul style={{ listStyle: "none", padding: 0 }}>
        {todos.map(t => <TodoItem key={t.id} todo={t} onToggle={toggle} onDelete={remove} />)}
      </ul>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
Free sign-in required · Google login available

React Mini Project: Todo App Practice Exercises

Check what you learned in “React Mini Project: Todo App”.

/ 3 solved
Fill in the blank

Create a new array with the extra item instead of mutating the old one.

setTodos(todos.(newTodo));
✓ Correct! Not quite. Try again.
Multiple choice

How do you remove an item from a state array?

✓ Correct! Not quite. Try again.
Code challenge

Render the todos as <li> elements and show "2 left" in a <p> (count todos where done is false).

Write the code in the live editor. Your answer is checked automatically.

Free sign-in required · Google login available

to save your exercise progress.
Finished this lesson?
Mark it complete to track your progress.

Stuck on something?

Ask the community in the CodesInTech forum.

Need a developer?

Learning on your own is great — and when you need professional help, our team builds custom software, websites and mobile apps for clients worldwide.

Explore Our Services