Создание корзины покупок в Next.js с Context API и useReducer

Корзина — ключевой компонент любого e‑commerce сайта: она хранит выбранные товары и передаёт их на оплату. В Next.js простая, масштабируемая реализация достигается через Context API для обмена данными между компонентами и useReducer для централизованного управления состоянием.
Что будет в статье
- Создание страницы товара и кнопок добавления/удаления.
- Контекст корзины: провайдер, редьюсер, обработчики add/remove.
- Как подключить контекст в _app и в компонент товара.
- Дополнительные функции, тесты, альтернативы и практические советы.
Важно: примеры кода ориентированы на функциональные компоненты React/Next.js и современный синтаксис hooks.
Создание страницы товара
В папке pages создайте файл Product.jsx, который рендерит один товар. Компонент принимает id, name и price и показывает кнопку добавления в корзину или удаления из неё.
Пример компонента товара:
export default function Product({ id, name, price }) {
return (
{name}
{price}
);
}Кнопка должна переключаться на “Remove from Cart”, если товар уже в корзине. Для этого понадобится отслеживать состояние корзины через Context API и useReducer.
Создание контекста корзины
Context API позволяет делиться данными между компонентами без “прокидывания” пропсов через всё дерево. Это удобно для навбара, страницы товара, страницы оформления заказа.
Создайте файл context/cartContext.js и объявите контекст:
import { createContext } from "react";
export const CartContext = createContext({
items: [],
});CartContext по умолчанию содержит массив items.
Далее нужно создать провайдера контекста — компонент, который будет предоставлять значение контекста всем потребителям.
Редьюсер корзины (useReducer)
useReducer помогает управлять более сложной логикой состояния, чем useState. Он принимает функцию редьюсера и начальное состояние, возвращая текущее состояние и dispatch для отправки действий.
Пример редьюсера:
const cartReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "ADD":
return {
...state,
items: payload.items,
};
case "REMOVE":
return {
...state,
items: payload.items,
};
default:
throw new Error("No case for that type");
}
};Редьюсер обрабатывает действия “ADD” и “REMOVE”. В реальном приложении можно расширить набор действий: “UPDATE_QUANTITY”, “CLEAR”, “SET_FROM_STORAGE” и т.д.
Провайдер контекста с useReducer
Объедините провайдер и редьюсер. Ниже — расширенный провайдер, который экспортирует items, addToCart и removeFromCart:
import { createContext, useReducer } from "react";
export const CartContext = createContext({
items: [],
});
const initialState = { items: [] };
const cartReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "ADD":
return {
...state,
items: payload.items,
};
case "REMOVE":
return {
...state,
items: payload.items,
};
default:
throw new Error("No case for that type");
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, initialState);
const addToCart = (product) => {
const updatedCart = [...state.items, product];
dispatch({
type: "ADD",
payload: {
items: updatedCart,
},
});
};
const removeFromCart = (id) => {
const updatedCart = state.items.filter(
(currentProduct) => currentProduct.id !== id
);
dispatch({
type: "REMOVE",
payload: {
items: updatedCart,
},
});
};
const value = {
items: state.items,
addToCart,
removeFromCart,
};
return {children} ;
};Примечание: для реальности хранения корзины между сессиями добавьте сохранение в localStorage (или серверное хранилище) и действие “SET_FROM_STORAGE”.
Важно: не храните чувствительные данные платежей в контексте.
Подключение провайдера в приложении
Обёрните корневой компонент приложения в CartProvider, чтобы весь стек компонентов мог использовать корзину.
В _app.js или pages/_app.js:
import { CartProvider } from "../context/cartContext";
function MyApp({ Component, pageProps }) {
return (
);
}
export default MyApp;Потребление контекста в компоненте товара
В компоненте Product используйте useContext для доступа к items, addToCart и removeFromCart.
import { useContext, useEffect, useState } from "react";
import { CartContext } from "../context/cartContext";
export default function Product({ id, name, price }) {
const { items, addToCart, removeFromCart } = useContext(CartContext);
const [exists, setExists] = useState(false);
useEffect(() => {
const inCart = items.find((item) => item.id === id);
if (inCart) {
setExists(true);
} else {
setExists(false);
}
}, [items, id]);
return (
{name}
{price}
{exists ? (
) : (
)}
);
}Кнопки используют функции из контекста; useEffect следит за изменениями items и обновляет локальное состояние exists.
Добавление дополнительных возможностей
Используйте те же принципы для:
- изменения количества товара (action: “UPDATE_QUANTITY”),
- очистки корзины (“CLEAR”),
- синхронизации с сервером или localStorage,
- расчёта итоговой суммы и налогов.
Когда этот подход не подходит (примеры отказа)
- Очень большое приложение с множеством взаимодействий и сложной бизнес‑логикой: лучше использовать специализированные стейт‑менеджеры (Redux, MobX).
- Необходимость высокопроизводительной оптимизации большого количества пересечений состояния: Context при частых обновлениях может вызывать лишние перерендеры.
- Нужно сложное кеширование/схемы синхронизации с сервером (используйте React Query + отдельный store).
Альтернативные подходы
- Redux Toolkit: хорошо для большого приложения, предоставляет DevTools и predictable state flow.
- Zustand: лёгкий, быстрый глобальный стейт с минимальным API.
- Recoil: атомарный подход к состоянию для сложных связей между компонентами.
- Local component state + lifting: для очень простых приложений можно хранить корзину в ближайшем общем родителе.
Ментальные модели и эвристики
- Single Source of Truth: храните список товаров в одном месте (провайдер), остальные компоненты только читают/запрашивают изменения.
- Actions over mutations: изменяйте состояние через понятные действия (ADD, REMOVE, UPDATE_QUANTITY).
- Separation of concerns: UI не должен напрямую менять формат хранения (например, структура items), а должен использовать API провайдера.
Чек-листы по ролям
Разработчик frontend:
- Реализовать CartProvider и cartReducer.
- Подключить _app.js к CartProvider.
- Обновить компонент Product для использования контекста.
- Написать unit тесты для редьюсера.
QA инженер:
- Покрыть сценарии: добавить товар, удалить товар, дубли, изменение количества.
- Проверить поведение при пустой корзине.
- Проверить синхронизацию с localStorage (если есть).
Product owner / менеджер:
- Убедиться, что UX кнопок для добавления/удаления ожидаем и понятен пользователю.
Критерии приёмки
- При добавлении товара он появляется в глобальном items.
- При удалении товар исчезает из items.
- Кнопка в компоненте товара корректно переключается между “Add to Cart” и “Remove from Cart”.
- Корзина сохраняет состояние между переходами страниц (если реализовано сохранение).
Тест‑кейсы (кратко)
- Добавление одного товара: ожидание — items.length увеличился на 1.
- Удаление товара по id: ожидание — items не содержит товар с данным id.
- Добавление одного и того же товара дважды: определить ожидаемое поведение (дубликат или увеличение quantity).
- Поведение после перезагрузки страницы при включённом persistence.
Шпаргалка / сниппеты
- Инициализация провайдера: CartProvider выше.
- Добавление товара: dispatch с типом “ADD” и payload.items = […state.items, product].
- Удаление товара: filter по id и dispatch с типом “REMOVE”.
Совместимость и миграция
- Next.js 12/13: подход сохраняется, но при использовании App Router и серверных компонентов (server components) хранение контекста на клиенте требует оборачивания клиентской части. Помните про директиву “use client” в компонентах, которые используют useState/useEffect/useContext.
- Если переходите с Redux: можно постепенно мигрировать редьюсеры, делая мостовой слой между Redux и Context.
Простые меры безопасности и приватности
- Не храните в состоянии платежные данные (CVV, полные номера карт).
- Если синхронизируете корзину в бекенд, аутентифицируйте запросы и проверяйте права доступа.
1‑строчный глоссарий
- Context API: механизм React для передачи данных через дерево компонентов без прокидывания пропсов.
- useReducer: хук для управления сложной логикой состояния через редьюсер и dispatch.
Резюме
- Context + useReducer — удобный способ реализовать корзину в средних по сложности приложениях.
- Для больших проектов рассмотрите специализированные стейт‑менеджеры.
- Не забывайте о персистентности, тестах и безопасности.
Ключевые шаги: создать CartContext, реализовать cartReducer и CartProvider, обернуть приложение в провайдер, использовать useContext в компоненте продукта и управлять отображением кнопок через локальное состояние.
Похожие материалы
Скачать и установить RoboForm на Windows
Виджет батареи Lenovo Vantage: как включить
Malwarebytes Browser Guard — установка и советы
Как скачать видео с Vine
Как защитить умный дом и интернет‑устройства