React и Firebase: CRUD с Firestore

React в сочетании с Firebase позволяет быстро создавать отзывчивые приложения с минимальным бэкендом. Если вы уже знакомы с React, интеграция Firestore — естественный следующий шаг. Здесь мы подробно пройдём весь путь: от настройки проекта до полного CRUD-примера и рекомендаций по надёжности и безопасности.
Что вы узнаете
- Как подключить React-приложение к Firebase Firestore
- Как записывать, читать, обновлять и удалять документы
- Когда использовать addDoc vs setDoc
- Практические рекомендации, чеклисты и распространённые ошибки
Важно: определения в одну строку
- Firestore — облачная NoSQL база данных от Firebase, оптимизированная для реального времени.
- addDoc — добавляет документ в коллекцию с автоматически сгенерированным ID.
- setDoc — записывает или перезаписывает документ по заданному ID, можно использовать merge для частичных изменений.
Подключение React к Firebase Firestore
Если вы ещё не создали проект в Firebase, зайдите в консоль Firebase и создайте новый проект, подключите Firestore. Процесс простой, особенно если React-приложение уже создано.
Создайте папку firebase_setup внутри src и файл firebase.js. Вставьте конфигурацию, которую выдаёт консоль Firebase:
import { initializeApp } from "firebase/app";
import {getFirestore} from "@firebase/firestore"
const firebaseConfig = {
apiKey: process.env.REACT_APP_apiKey,
authDomain: process.env.REACT_APP_authDomain,
projectId: process.env.REACT_APP_projectId,
storageBucket: process.env.REACT_APP_storageBucket,
messagingSenderId: process.env.REACT_APP_messagingSenderId,
appId: process.env.REACT_APP_appId,
measurementId: process.env.REACT_APP_measurementId
};
const app = initializeApp(firebaseConfig);
export const firestore = getFirestore(app)
Переменная firestore содержит окружение Firestore — используйте её везде, где делаете запросы к базе.
Примечание о секретах: код использует .env для маскировки конфигурации. Для production рассмотрите использование защищённого хранилища секретов (CI/CD secret store, Vault, секреты хоста) и ограничение прав ключей.
Установите библиотеки firebase и uuid:
npm install firebase uuid
uuid опционален — можно использовать его как дополнительное уникальное поле внутри документа.
Демонстрация: что мы строим
Ниже — полный пример DOM и состояний, которые использует статья. Код оставлен в исходном виде, чтобы вы могли прямо вставить его в проект.
import './App.css';
import { useEffect, useState } from 'react';
import { addDoc, collection, setDoc, deleteDoc, doc, query, onSnapshot } from "firebase/firestore";
import { firestore } from './firebase_setup/firebase';
import { v4 as uuidv4 } from 'uuid';
Компонент App:
function App() {
const [info, setInfo] = useState([])
const [isUpdate, setisUpdate] = useState(false)
const [docId, setdocId] = useState("")
const [detail, setDetail] = useState("")
const [ids, setIds] = useState([])
return (
{info.map((data, index)=>
{data}
)}
);
}
export default App;
Запись данных в Firestore
Для добавления документов используйте addDoc или setDoc. addDoc удобен тем, что генерирует уникальный ID автоматически.
Пример импорта зависимостей показан выше. Теперь создаём обработчики для изменения поля и для отправки формы:
const handledatachange = (e) => {
setDetail(e.target.value)
};
const submithandler = (e) => {
e.preventDefault()
const ref = collection(firestore, "test_data")
let data = {
uuid: uuidv4(),
testData: detail
}
try {
addDoc(ref, data)
} catch(err) {
console.log(err)
}
setDetail("")
}
Замечания и когда лучше addDoc vs setDoc:
- addDoc: удобен при вставке записей без заранее известного ID (например, ленты сообщений).
- setDoc: нужен, когда вы контролируете ID (например, пользовательский профиль с ID = UID) или хотите перезаписать/объединить документ по известному пути.
Совет: храните служебные уникальные идентификаторы (uuid) внутри документа, если вам нужно независимое от Firestore ID поле для ссылок или кросс-системной синхронизации.
Чтение данных из Firestore
Используем useEffect и onSnapshot для подписки на изменения в коллекции. onSnapshot даёт snapshot, который автоматически обновляет клиент при изменении данных на сервере.
useEffect(() => {
const getData = async () => {
const data = await query(collection(firestore, "test_data"));
onSnapshot(data, (querySnapshot) => {
const databaseInfo = [];
const dataIds = []
querySnapshot.forEach((doc) => {
databaseInfo.push(doc.data().testData);
dataIds.push(doc.id)
});
setIds(dataIds)
setInfo(databaseInfo)
});
}
getData()
}, [])
Пояснения:
- onSnapshot слушает изменения на сервере и вызывает колбэк с актуальным снимком.
- setInfo заполняет массив значений для отображения, setIds хранит соответствующие идентификаторы документов для операций Update/Delete.
Обновление данных в Firestore
Для обновления используйте setDoc с опцией { merge: true } чтобы не перезаписать весь документ.
const handleupdate = (e) => {
setisUpdate(true)
setDetail(e.target.parentNode.children[0].textContent)
setdocId(e.target.parentNode.children[0].getAttribute("data-id"))
};
const handlesubmitchange = async (e) => {
e.preventDefault()
const docRef = doc(firestore, 'test_data', docId);
const updatedata = await {
testData: detail
};
await setDoc(docRef, updatedata, { merge:true })
.then(console.log("Data changed successfully"))
setisUpdate(false)
setDetail("")
}
Логика: кнопка Edit наполняет input значением из выбранного документа, меняет isUpdate на true и сохраняет id документа. Кнопка Update отправляет изменения в Firestore.
Удаление данных из Firestore
Для удаления используйте deleteDoc, передав docRef по ID документа.
const handledelete = async (e) => {
const docRef = doc(firestore, 'test_data', e.target.parentNode.children[0].getAttribute("data-id"));
await deleteDoc(docRef)
.then(() => {
console.log(`${e.target.parentNode.children[0].textContent} has been deleted successfully.`)
})
.catch(error => {
console.log(error);
})
}
После выполнения deleteDoc удалённый документ исчезнет и из базы, и из локального списка благодаря подписке onSnapshot.
Практические рекомендации и лучшие практики
- Разделяйте логику доступа к Firestore в отдельные сервисные модули (api/firestore.js). Это упростит тестирование и замену логики.
- Ограничивайте правила безопасности Firestore (Security Rules) по необходимости: проверяйте auth.uid, права на запись и чтение.
- В продакшене избегайте хранения секретов в клиентском коде; используйте серверные функции (Cloud Functions) для чувствительных операций.
- Используйте пагинацию и индексирование для больших коллекций (limit, startAfter) — onSnapshot на большой коллекции может дорого обходиться.
- Обрабатывайте сетевые ошибки и показывайте пользователю статусы загрузки/ошибки.
Important: тестируйте правила безопасности отдельно от UI — включите эмуляторы Firebase для локальной отладки.
Отладка и распространённые ошибки
- Неправильные правила Firestore — операция возвращает permission-denied.
- Ошибка CORS/авторизации при попытке обратиться к приватным ресурсам — проверьте настройки аутентификации.
- Несоответствие типов данных — Firestore строго хранит типы; главное поле testData должно быть строкой, если код ожидает строку.
- Подписки onSnapshot множатся при повторном монтировании — отменяйте подписку при размонтировании компонента.
Пример отмены подписки:
useEffect(() => {
const q = query(collection(firestore, "test_data"))
const unsubscribe = onSnapshot(q, (snapshot) => { /* обработка */ })
return () => unsubscribe()
}, [])
Малые методологии (cheat sheet)
- Начать: create React app → firebase console → Firestore → получить конфиг → добавить firebase.js → установить зависимости.
- Локально: запустить Firebase emulators для Firestore и Auth.
- Производство: настроить Security Rules и ограничить права доступа.
Рольовые чеклисты
Разработчик frontend:
- Инициализировал firebase.js и экспортировал firestore
- Реализовал addDoc, onSnapshot, setDoc, deleteDoc
- Добавил обработку ошибок и индикаторы загрузки
DevOps / SRE:
- Настроил эмуляторы для тестов
- Задеплоил правила безопасности и проверил разрешения
Product Owner:
- Подтвердил сценарии CRUD и требования к безопасности
- Утвердил ограничения на объём данных и требования к производительности
Решение выбора: addDoc или setDoc (дерево решений)
flowchart TD
A[Нужен ли заданный ID?] -->|Да| B[Использовать setDoc]
A -->|Нет| C[Использовать addDoc]
B --> D{Требуется объединение полей?}
D -->|Да| E[setDoc с {merge:true}]
D -->|Нет| F[setDoc перезаписывает документ]
C --> G[Firestore генерирует ID автоматически]
Критерии приёмки
- Пользователь может создать запись через форму и увидеть её в списке.
- Запись синхронизируется в реальном времени между несколькими клиентами.
- Пользователь может отредактировать запись и увидеть обновления без перезагрузки.
- Пользователь может удалить запись, и она пропадёт у всех клиентов.
- Правила безопасности запрещают неавторизованный доступ.
Когда этот подход не подходит (контрпримеры)
- Если вы нуждаетесь в сложных транзакциях между множеством сущностей — Firestore поддерживает транзакции, но для очень сложных бизнес-процессов лучше использовать серверный API.
- Когда нужно гарантированное последовательное согласование с внешними сервисами — серверная логика даёт больше контроля.
Короткое резюме
- Firestore + React позволяют быстро строить приложения с минимумом бэкенд-логики.
- Используйте onSnapshot для реального времени, addDoc для быстрых вставок и setDoc для контролируемых обновлений.
- Настройте правила безопасности и подумайте о хранении секретов для production.
Extras
- Тестируйте в эмуляторе Firebase, прежде чем деплоить в прод.
- Сохраняйте код доступа к Firestore в сервисах, а не в UI-логике.
Спасибо за чтение — теперь вы можете интегрировать Firestore в своё React-приложение и реализовать CRUD-сценарии с учётом безопасности и масштабируемости.