Гид по технологиям

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

5 min read Разработка Обновлено 13 Dec 2025
Корзина в Next.js: Context API + useReducer
Корзина в 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”.
  • Корзина сохраняет состояние между переходами страниц (если реализовано сохранение).

Тест‑кейсы (кратко)

  1. Добавление одного товара: ожидание — items.length увеличился на 1.
  2. Удаление товара по id: ожидание — items не содержит товар с данным id.
  3. Добавление одного и того же товара дважды: определить ожидаемое поведение (дубликат или увеличение quantity).
  4. Поведение после перезагрузки страницы при включённом 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 в компоненте продукта и управлять отображением кнопок через локальное состояние.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Скачать и установить RoboForm на Windows
Менеджеры паролей

Скачать и установить RoboForm на Windows

Виджет батареи Lenovo Vantage: как включить
Техника

Виджет батареи Lenovo Vantage: как включить

Malwarebytes Browser Guard — установка и советы
Безопасность

Malwarebytes Browser Guard — установка и советы

Как скачать видео с Vine
Руководство

Как скачать видео с Vine

Как защитить умный дом и интернет‑устройства
Безопасность

Как защитить умный дом и интернет‑устройства

Удаление и переустановка повреждённого драйвера принтера
Windows

Удаление и переустановка повреждённого драйвера принтера