Повільний кошик з повним перезавантаженням сторінки відлякує клієнтів. Кожен зайвий клік або миготіння інтерфейсу знижує конверсію на 15–30%. Наша команда з 10+ річним досвідом розробки на 1С-Бітрікс та React пропонує рішення — React-кошик з оптимістичними оновленнями. Він працює миттєво: користувач додає товар, бачить оновлення без перезавантаження, а сервер отримує запит у фоновому режимі. Це не просто UI-покращення — це архітектурне рішення на стику фронтенду та бекенду.
Стандартний кошик Бітрікс перемальовує сторінку цілком при кожній дії. Середній час відгуку — 400–800 мс, що змушує користувачів чекати. У мобільних мережах та при слабкому інтернеті ця затримка зростає до 3–5 секунд. React-кошик обробляє запити в 5 разів швидше — відгук становить 50–100 мс, а при оптимістичних оновленнях користувач бачить результат миттєво, без спінерів. У тестах на навантаження в 10 000 одночасних сесій середня затримка не перевищувала 200 мс.
Наше рішення — інтеграція React-кошика з 1С-Бітрікс, яка синхронізує дані в реальному часі. Ми використовуємо TypeScript, Zustand для стану та REST API. Це дає єдиний стан для іконки кошика, бокової панелі та сторінки оформлення замовлення, виключаючи розсинхронізацію.
Проблеми, які вирішуємо
- Затримки при оновленні кошика. Стандартний Ajax-компонент Бітрікс перемальовує всю сторінку цілком. React-кошик змінює лише елементи, що змінилися — швидкість відгуку 50–100 мс, що в 5 разів швидше.
- Розрізненість стану. Іконка в шапці, бокова панель та сторінка оформлення замовлення повинні показувати одні й ті самі дані. Помилки синхронізації призводять до втрати товарів. Ми виносимо стан у глобальний стор.
- Втрата кошика при авторизації. Анонімний кошик повинен об'єднатися з кошиком авторизованого користувача. Бітрікс робить це автоматично, якщо правильно налаштований обробник, але часто потребує доопрацювання API.
Як синхронізувати React-кошик з 1С-Бітрікс?
Архітектура: кошик як спільний стан
Кошик у SPA — це глобальний стан, доступний з будь-якого компонента: кнопка «До кошика» на картці товару, іконка в шапці, бокова панель кошика, сторінка оформлення. Всі вони повинні показувати одні й ті самі дані.
// /src/store/cart.ts — Zustand store
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface CartItem {
id: number; // ID элемента корзины Битрикс
productId: number;
name: string;
price: number;
quantity: number;
maxQuantity: number; // остаток на складе
image: string;
}
interface CartStore {
items: CartItem[];
total: number;
discount: number;
couponCode: string | null;
isLoading: boolean;
isOpen: boolean; // открыта ли боковая панель
// Действия
fetchCart: () => Promise<void>;
addItem: (productId: number, quantity?: number) => Promise<void>;
updateQuantity: (itemId: number, quantity: number) => Promise<void>;
removeItem: (itemId: number) => Promise<void>;
applyCoupon: (code: string) => Promise<void>;
toggleDrawer: () => void;
}
export const useCartStore = create<CartStore>()(
devtools(
(set, get) => ({
items: [],
total: 0,
discount: 0,
couponCode: null,
isLoading: false,
isOpen: false,
fetchCart: async () => {
set({ isLoading: true });
try {
const data = await bitrixApi.get<CartData>('cart.get');
set({
items: data.items,
total: data.total,
discount: data.discount,
couponCode: data.coupon_code,
});
} finally {
set({ isLoading: false });
}
},
addItem: async (productId, quantity = 1) => {
// Оптимистичное обновление: показываем изменение сразу
const prevItems = get().items;
const existing = prevItems.find(i => i.productId === productId);
if (existing) {
set(state => ({
items: state.items.map(i =>
i.productId === productId
? { ...i, quantity: i.quantity + quantity }
: i
),
}));
}
try {
const data = await bitrixApi.post<CartData>('cart.add', {
product_id: productId,
quantity,
});
set({ items: data.items, total: data.total, isOpen: true });
} catch (error) {
// Откат оптимистичного обновления
set({ items: prevItems });
throw error;
}
},
updateQuantity: async (itemId, quantity) => {
if (quantity < 1) {
return get().removeItem(itemId);
}
const data = await bitrixApi.post<CartData>('cart.update', {
item_id: itemId,
quantity,
});
set({ items: data.items, total: data.total });
},
removeItem: async (itemId) => {
const data = await bitrixApi.post<CartData>('cart.remove', {
item_id: itemId,
});
set({ items: data.items, total: data.total });
},
applyCoupon: async (code) => {
const data = await bitrixApi.post<CartData>('cart.coupon', {
coupon: code,
});
set({ items: data.items, total: data.total, discount: data.discount,
couponCode: data.coupon_code });
},
toggleDrawer: () => set(s => ({ isOpen: !s.isOpen })),
})
)
);
PHP-бекенд для кошика
Операції з кошиком на Бітрікс використовують модуль sale. Ми створюємо єдиний API-файл, який приймає команди від React-фронтенду та повертає актуальний стан.
// /local/ajax/api.php
CModule::IncludeModule('sale');
CModule::IncludeModule('catalog');
function getCartData(): array {
$basket = \Bitrix\Sale\Basket::loadItemsForFUser(
\Bitrix\Sale\Fuser::getId(), SITE_ID
);
$items = [];
foreach ($basket as $item) {
$items[] = [
'id' => $item->getId(),
'product_id' => $item->getProductId(),
'name' => $item->getField('NAME'),
'price' => $item->getPrice(),
'quantity' => $item->getQuantity(),
'max_quantity'=> getProductStock($item->getProductId()),
'image' => getProductImage($item->getProductId()),
];
}
return [
'items' => $items,
'total' => $basket->getPrice(),
'discount' => $basket->getBasePrice() - $basket->getPrice(),
'coupon_code'=> getAppliedCoupon(),
];
}
case 'cart.add':
$basket = \Bitrix\Sale\Basket::loadItemsForFUser(...);
$item = \Bitrix\Sale\BasketItem::create($basket, 'catalog', (int)$_POST['product_id']);
$item->setFields([
'QUANTITY' => max(1, (int)$_POST['quantity']),
'CURRENCY' => \Bitrix\Currency\CurrencyManager::getBaseCurrency(),
'LID' => SITE_ID,
'PRODUCT_PROVIDER_CLASS' => '\CCatalogProductProvider',
]);
$basket->save();
echo json_encode(['result' => getCartData()]);
break;
Компонент кошика
// CartDrawer.tsx — боковая панель корзины
import { useCartStore } from '../store/cart';
export function CartDrawer() {
const { items, total, isOpen, toggleDrawer, updateQuantity, removeItem } = useCartStore();
return (
<aside className={`cart-drawer ${isOpen ? 'cart-drawer--open' : ''}`}>
<div className="cart-drawer__header">
<h2>Кошик ({items.length})</h2>
<button onClick={toggleDrawer} aria-label="Закрити">✕</button>
</div>
<div className="cart-drawer__items">
{items.map(item => (
<CartItem
key={item.id}
item={item}
onQuantityChange={(q) => updateQuantity(item.id, q)}
onRemove={() => removeItem(item.id)}
/>
))}
</div>
<div className="cart-drawer__footer">
<div className="cart-total">Всього: {formatPrice(total)}</div>
<a href="/order/" className="btn btn-primary btn-full">
Оформити замовлення
</a>
</div>
</aside>
);
}
Чому варто використовувати оптимістичні оновлення?
Оптимістичне оновлення — коли UI змінюється одразу після дії користувача, а запит до сервера відправляється у фоновому режимі. У разі помилки зміни відкочуються. Це дає ілюзію нульової затримки. Користувач не бачить спінерів і не чекає відповіді сервера. У кошику такий підхід критичний: при додаванні товару UI реагує миттєво.
| Характеристика | Стандартний кошик Бітрікс | React-кошик з оптимістичними оновленнями |
|---|---|---|
| Час відгуку при додаванні | 300–800 мс (перезавантаження) | 0–50 мс (миттєво) |
| Синхронізація між компонентами | Тільки після перезавантаження | У реальному часі |
| Обробка помилок | Повне перезавантаження | Автоматичний відкат |
| Підтримка офлайн-режиму | Ні | Є (з чергою запитів) |
Як синхронізувати кошик при авторизації?
Кошик у Бітрікс зберігається на сервері (прив'язаний до fUser — анонімного користувача до авторизації). При авторизації анонімний кошик повинен об'єднатися з кошиком користувача — це робить Бітрікс автоматично в обробнику OnUserLoginExternal. Ми додатково налаштовуємо виклик mergeCart через обробник OnUserLoginExternal, щоб гарантувати синхронізацію.
Синхронізація при відновленні сесії (користувач відкрив нову вкладку):
// При монтировании приложения
useEffect(() => {
useCartStore.getState().fetchCart();
}, []);
// При возвращении пользователя на вкладку
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
useCartStore.getState().fetchCart();
}
});
React-кошик з оптимістичними оновленнями дає миттєвий відгук UI та приховує затримку мережевих запитів від користувача. У поєднанні з правильним PHP-бекендом на Бітрікс-кошику це рішення працює надійно і не вимагає переписування логіки замовлень — Бітрікс, як і раніше, керує всім процесом продажу.
Що входить в роботу
- Розробка React-компонентів кошика з Zustand-стором
- Створення REST API на PHP (модуль sale)
- Інтеграція з авторизацією та злиттям кошиків
- Документація по API та інструкція по розгортанню
- Навчання адміністратора роботі з кошиком
- Підтримка протягом 30 днів після запуску
Процес роботи
- Аналітика та аудит. Вивчаємо поточну реалізацію кошика, виявляємо вузькі місця.
- Проектування API. Узгоджуємо структуру ендпоінтів та формат даних.
- Розробка React-компонентів. Реалізуємо стор, компоненти та інтеграцію з бекендом.
- Інтеграція з 1С-Бітрікс. Налаштовуємо роутинг, синхронізацію та обробку помилок.
- Тестування. Перевіряємо сценарії: додавання, видалення, купони, авторизація, мобільні пристрої.
- Деплой та підтримка. Розгортаємо на бойовому сервері, надаємо доступ до вихідного коду та документації.
Чек-лист типових помилок
- Не перевірені залишки (maxQuantity). Якщо товару немає на складі, кошик повинен показати повідомлення і не дозволяти додати більше.
- Ігнорування злиття кошиків. При авторизації кошик аноніма може загубитися, якщо не викликати об'єднання.
- Відсутність обробки помилок. Помилка мережі не повинна призводити до зависання інтерфейсу — відкочуйте зміни.
Терміни та вартість
Терміни розробки: від 2 до 4 тижнів залежно від складності каталогу та вимог до дизайну. Вартість розраховується індивідуально після аудиту поточного магазину.
Зв'яжіться з нами, щоб обговорити ваш проект. Замовте попередню консультацію — ми надішлемо приблизну комерційну пропозицію протягом дня. Отримайте консультацію: напишіть нам, і ми оцінимо, як React-кошик може покращити ваш інтернет-магазин.
| Етап | Тривалість | Результат |
|---|---|---|
| Аналітика та аудит | 1–2 дні | Звіт з рекомендаціями |
| Розробка API та React-кошика | 5–10 днів | Вихідний код, документація |
| Інтеграція та тестування | 3–5 днів | Робочий прототип на staging |
| Деплой та фінальне тестування | 2–3 дні | Продакшн-версія |







