Мини-проект: список задач (To-Do)

Собираем всё изученное в одно маленькое приложение — список задач: добавляем, показываем и удаляем дела.

Что строим

Классический мини-проект: поле ввода вверху, список задач ниже, кнопка добавления и удаление по тапу. Здесь встретятся почти все темы курса: useState, TextInput, FlatList, обработка касаний.

Состояние приложения

Нам нужны два состояния: текст в поле ввода и сам список задач.

import { useState } from "react";

function useTodos() {
  const [text, setText] = useState("");
  const [todos, setTodos] = useState([]);
  // ... логика ниже
}

Добавление задачи

При нажатии «Добавить» создаём новый объект задачи и кладём его в массив. Важно: не мутируем старый массив, а создаём новый через spread — иначе React не увидит изменения.

function addTodo() {
  if (text.trim() === "") return; // не добавляем пустое
  const newTodo = { id: Date.now().toString(), title: text };
  setTodos([...todos, newTodo]); // новый массив, не push!
  setText(""); // очищаем поле
}

Удаление задачи

Удаляем фильтрацией: оставляем все задачи, кроме той, чей id совпал. Снова получаем новый массив.

function removeTodo(id) {
  setTodos(todos.filter((t) => t.id !== id));
}

Собираем экран

import { useState } from "react";
import { View, Text, TextInput, Button, FlatList, Pressable } from "react-native";

export default function TodoApp() {
  const [text, setText] = useState("");
  const [todos, setTodos] = useState([]);

  function addTodo() {
    if (text.trim() === "") return;
    setTodos([...todos, { id: Date.now().toString(), title: text }]);
    setText("");
  }

  function removeTodo(id) {
    setTodos(todos.filter((t) => t.id !== id));
  }

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <TextInput
        value={text}
        onChangeText={setText}
        placeholder="Новая задача"
      />
      <Button title="Добавить" onPress={addTodo} />
      <FlatList
        data={todos}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <Pressable onPress={() => removeTodo(item.id)}>
            <Text>{item.title}</Text>
          </Pressable>
        )}
      />
    </View>
  );
}

Тап по задаче удаляет её. Всё приложение — это состояние плюс три функции: добавить, удалить, нарисовать.

Логика списка — обычный JS

Сердце приложения — операции с массивом, и это чистый JavaScript. Прогоним добавление и удаление (запускаемый пример):

let todos = [];

// добавляем две задачи (новый массив каждый раз)
todos = [...todos, { id: "1", title: "Купить хлеб" }];
todos = [...todos, { id: "2", title: "Позвонить маме" }];
console.log("После добавления:", todos.length);

// удаляем задачу с id "1"
todos = todos.filter((t) => t.id !== "1");
console.log("После удаления:", todos.length);
console.log("Осталось:", todos[0].title);

Вывод:

После добавления: 2
После удаления: 1
Осталось: Позвонить маме

Куда развивать

  • Отметка «выполнено» (галочка) — добавить поле done.
  • Сохранение в AsyncStorage, чтобы задачи не пропадали.
  • Разбить на компоненты: TodoItem, TodoInput.

Итог

  • Мини-проект объединяет состояние, ввод, список и касания.
  • Состояние-массив меняют иммутабельно: spread для добавления, filter для удаления.
  • Всё приложение сводится к состоянию и нескольким функциям над ним.
Проверьте себя
1. Как правильно добавить элемент в массив-состояние?
Atodos.push(item)
BsetTodos([...todos, item]) — создаём новый массив
Ctodos[todos.length] = item
DsetTodos(item)
2. Как удалить задачу из списка по id?
Atodos.splice(id, 1)
BsetTodos(todos.filter((t) => t.id !== id))
Cdelete todos[id]
DsetTodos(null)
3. Почему нельзя использовать todos.push для добавления в состояние?
Apush слишком медленный
Bpush мутирует старый массив, и React не увидит изменения — экран не перерисуется
Cpush не существует в React Native
Dpush добавляет в начало массива
Поддержать проект