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

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

5 min read Frontend Обновлено 09 Jan 2026
Корзина в Next.js: Context API + useReducer
Корзина в Next.js: Context API + useReducer

Товары в корзине интернет-магазина

В интернет-магазине корзина — ключевой компонент: она хранит выбранные товары, обеспечивает добавление/удаление и передаёт данные на страницы оформления заказа. В Next.js для этого удобно использовать Context API для шаринга состояния между компонентами и useReducer для управления сложной логикой изменения состояния.

Краткая идея

  • Context API: сервис для передачи данных через дерево компонентов без проп-дриллинга. Определяете контекст, оборачиваете приложение провайдером и читаете значения через useContext.
  • useReducer: альтернатива useState для более сложных операций над состоянием. Принимает редьюсер и начальное состояние; возвращает текущее состояние и dispatch.

Определения в одну строку:

  • Context API — глобальный контейнер данных для React-компонентов.
  • useReducer — паттерн «action → reducer → новый state».

Пример: Страница продукта

Ниже — минимальный компонент страницы товара. Он принимает id, name, price и рендерит кнопку, которая добавляет или удаляет товар из корзины.

export default function Product({id, name, price}) {  
  return (  
    

{name}

{price}

) }

Обратите внимание: реальная версия компонента будет подключать контекст и переключать текст/поведение кнопки в зависимости от того, есть ли товар в корзине.

Создание контекста корзины

Создайте файл context/cartContext.js и определите контекст с начальным значением. Ниже — полная, минимально необходимая реализация провайдера с редьюсером и экшенами “ADD” и “REMOVE”.

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};
};

Пояснения:

  • initialState содержит массив items.
  • cartReducer реагирует на экшены “ADD” и “REMOVE” и заменяет массив items в state на тот, который приходит в payload.
  • Провайдер экспортирует items, addToCart и removeFromCart через value.

Важно: логика добавления/удаления здесь простая (полная замена массива). При необходимости вы можете менять структуру payload и делать иммутабельные обновления, учитывать количество единиц товара, проверять дубли и т.д.

Регистрация провайдера в приложении

Чтобы значения контекста были доступны по всему приложению, оберните корневой компонент провайдером CartProvider. В примере используется верхний компонент index.js (в Next.js обычно это _app.js):

import { CartProvider } from "../context/cartContext";

function MyApp({ Component, pageProps }) {
  return (
    
      
    
  );
}

export default MyApp;

Потребление контекста в компоненте продукта

В компоненте Product подключите useContext и возьмите items, addToCart и removeFromCart из CartContext. Также удобно отслеживать, есть ли текущий товар в корзине, через useState и useEffect, чтобы переключать поведение кнопки.

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.
  • addToCart и removeFromCart приходят из провайдера и выполняют dispatch.

Расширение функциональности

Добавить хранение количества, подсчёт общей суммы и синхронизацию с localStorage или сервером можно на той же основе:

  • Вместо простого списка товаров храните объекты { id, name, price, quantity }.
  • Экшены редьюсера: “ADD_ONE”, “DECREMENT”, “SET_QUANTITY”, “CLEAR_CART”.
  • В addToCart сначала проверяйте, есть ли товар в корзине, и увеличивайте quantity, а не дублируйте запись.
  • Для сохранения между сессиями синхронизируйте state с localStorage через useEffect в провайдере.

Фрагмент-идеи (логика, не полный код):

// при добавлении
const addToCart = (product) => {
  const exists = state.items.find(i => i.id === product.id);
  let updated;
  if (exists) {
    updated = state.items.map(i => i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i);
  } else {
    updated = [...state.items, { ...product, quantity: 1 }];
  }
  dispatch({ type: "SET_ITEMS", payload: { items: updated } });
};

Когда такой подход может не подойти

  • Очень большой объём состояния с сложными зависимостями и большим числом операций — может быть удобнее использовать специализированные менеджеры состояния (Redux, Zustand или MobX).
  • Если требуется сложная синхронизация с сервером в реальном времени (операции с конфликтами, optimistic updates), стоит внедрять дополнительные уровни абстракции.

Альтернативные подходы

  • Redux Toolkit: строгая типизация и удобные devtools для сложных приложений.
  • Zustand: лёгкий и быстрый для локального глобального состояния.
  • Apollo Client (для GraphQL): кеширование и синхронизация с сервером.

Выбор зависит от масштаба приложения, требований к отладке и опыту команды.

Мини-методология внедрения (шаги)

  1. Создать context/cartContext.js с CartContext и CartProvider.
  2. Определить initialState и cartReducer с набором экшенов.
  3. Реализовать addToCart/removeFromCart в провайдере и вернуть их в value.
  4. Обернуть приложение провайдером.
  5. Подключить контекст в компоненте продукта и реализовать UI-логику кнопки.
  6. Написать модульные тесты и интеграционные проверки (см. Критерии приёмки).

Критерии приёмки

  • При добавлении товара он появляется в items провайдера.
  • Поведение кнопки на странице продукта переключается между “Добавить в корзину” и “Удалить из корзины”.
  • Удаление товара корректно исключает его по id.
  • При перезагрузке страницы (если реализован localStorage) содержимое корзины восстанавливается.

Минимальные тесты (acceptance):

  • Добавить товар → items.length увеличился на 1.
  • Добавить тот же товар снова (если реализована quantity) → quantity увеличилась.
  • Удалить товар → items не содержит элемент с этим id.

Чек-листы по ролям

Разработчик:

  • Создал cartContext.js
  • Реализовал редьюсер и экшены
  • Подключил провайдер в корень приложения
  • Написал компонент продукта с корректной подпиской на контекст
  • Добавил тесты на add/remove

Код-ревьюер:

  • Проверил иммутабельность обновлений state
  • Убедился в отсутствии утечек при множественных dispatch
  • Проверил обработку ошибок в редьюсере

Продакшн-операции:

  • При необходимости добавлена синхронизация с localStorage
  • Обработаны граничные случаи (пустой список, удаление несуществующего id)

Decision flow (Mermaid)

flowchart TD
  A[Клик на кнопку] --> B{Товар в корзине?}
  B -- Да --> C[Вызвать removeFromCart'id']
  B -- Нет --> D[Вызвать addToCart'product']
  C --> E[Обновить state через dispatch]
  D --> E
  E --> F[Обновить UI]

Безопасность и приватность

  • Не храните в клиентском стейте чувствительные данные пользователя.
  • Если корзина синхронизируется с backend, используйте авторизацию и защиту от CSRF/IDOR.

Итог

Context API + useReducer дают простую, хорошо масштабируемую основу для корзины в Next.js. Подход хорош для большинства небольших и средних магазинов: он лёгок в отладке, читабелен и не требует внешних библиотек. По мере роста приложения можно постепенно переходить к более мощным менеджерам состояния или добавлять серверную синхронизацию.

Сводка:

  • Начните с CartContext и простого редьюсера.
  • Экспортируйте функции addToCart/removeFromCart через value провайдера.
  • В компонентах пользуйтесь useContext и реагируйте на изменения items через useEffect.

Важно: адаптируйте структуру items (quantity, price, options) под требования вашего каталога и оформления заказа.

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство