🟨 React
Легкая
🕐 1 story point

React: Список задач (to-do) — добавление, удаление, отметка

Реализуй базовый to-do: добавление задачи в состояние, её удаление и отметку «выполнено».

Александр, тимлид React-стажировки

Продуктовый фокус. Контроль задач — ядро любого цифрового продукта. Научись работать со списками в состоянии.

Что нужно сделать

  • Создай состояние для массива задач: [{ id, title, done }].
  • Сделай поле ввода контролируемым: value из state; добавляй задачу по кнопке.
  • Реализуй отметку «выполнено» через чекбокс.
  • Реализуй удаление задачи по кнопке.

Финальный вид

Карточка с вводом, счётчиком задач, списком элементов, отметкой выполнения и удалением. Пример: Todo UI

💡 Подсказка
  • Модель: const [todos, setTodos] = useState([]) и const [newTitle, setNewTitle] = useState('').
  • Добавление: setTodos([...todos, { id: Date.now(), title: trimmed, done: false }]).
  • Переключение: setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t)).
  • Удаление: setTodos(todos.filter(t => t.id !== id)).
  • Следи за пустыми строками: trim() и блокируй добавление пустых задач.
👀 Решение
import React, { useState } from 'react';
import './styles.css';
 
export function TodoList() {
  const [todos, setTodos] = useState([]);
  const [newTitle, setNewTitle] = useState('');
 
  function handleAdd() {
    const title = newTitle.trim();
    if (!title) return;
    setTodos([...todos, { id: Date.now(), title, done: false }]);
    setNewTitle('');
  }
 
  function handleToggle(id) {
    setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
  }
 
  function handleDelete(id) {
    setTodos(todos.filter(t => t.id !== id));
  }
 
  return (
    <article className="card" data-testid="todo-card">
      <header>
        <h2 className="title">Sprint Focus: список задач</h2>
        <p className="subtitle">Добавляй задачи, отмечай выполненные, удаляй лишнее</p>
      </header>
 
      <div className="row">
        <input
          className="input"
          type="text"
          placeholder="Новая задача..."
          value={newTitle}
          onChange={e => setNewTitle(e.target.value)}
          aria-label="todo-input"
        />
        <button className="button" onClick={handleAdd}>Добавить</button>
      </div>
 
      <p className="counter" aria-label="counter">{todos.length}</p>
 
      <div className="list" data-testid="todo-list">
        {todos.length === 0 ? (
          <p className="empty">Нет задач — добавьте первую</p>
        ) : (
          todos.map((t) => (
            <div key={t.id} className={`item ${t.done ? 'done' : ''}`}>
              <input
                className="checkbox"
                type="checkbox"
                checked={t.done}
                onChange={() => handleToggle(t.id)}
                aria-label={`toggle-${t.title}`}
              />
              <span className="title-inline">{t.title}</span>
              <button className="button" onClick={() => handleDelete(t.id)}>Удалить</button>
            </div>
          ))
        )}
      </div>
    </article>
  );
}
 
export default function App() {
  return (
    <main className="challenge-container">
      <section>
        <TodoList />
      </section>
    </main>
  );
}

🧑‍💻 Это не баг! Это фича!

Редактор кода намеренно скрыт на мобильном.

Поверь, так лучше: я оберегаю тебя от искушения писать код в неидеальных условиях. Маленький экран и виртуальная клавиатура — не лучшие помощники для программиста.

📖 Сейчас: Изучи задачу, продумай решение. Действуй как стратег.

💻 Потом: Сядь за компьютер, открой сайт и реализуй все идеи с комфортом. Действуй как код-джедай!