Кейс React: как работает сканер штрихкода и как я его чинил в React-витрине
1. Что такое “витрина” на кассе
Если отбросить бизнес-термины, витрина терминала — это просто:
❗веб-страница (URL), открытая в браузере терминала
Обычно это:
https://terminal.company.com
По сути:
React-приложение + API + браузер
Кассир видит интерфейс, но под капотом — обычный фронтенд:
DOM
JavaScript
React
Redux (иногда)
2. Как на самом деле работает сканер штрихкода
Основная идея:
❗ Сканер — это НЕ “магия”, это клавиатура
Он работает как:
input device → как keyboard
Что происходит при сканировании
- Сканер “печатает” цифры:
4 8 2 9 3 8 1 2 3 4
Enter
Итоговая логика
цифры → собираются в строку (buffer)
↓
Enter
↓
поиск товара
↓
если найден:
- добавить в корзину
- или увеличить количество (+1)
3. Что пошло не так
У нас была новая витрина (React), и сканер не работал на двух разных кассах.
Это сразу намекнуло:
❗проблема не в железе, а во фронте
Дальше - отладка.
4. Проблемы, которые я нашел
❌ 1. Поиск не работал по штрихкоду
Работало:
поиск по названию — OK
поиск по barcode — НЕ работает
Причина:
product.title.includes(...)
но не было:
product.barcode ===input
❌ 2. Штрихкод не собирался в буфер
Сканер отправляет поток символов.
Нужно было:
let barcodeBuffer="";
Но логика сбрасывала буфер неправильно.
Ошибка:
каждый символ перезаписывал предыдущий
Решение:
barcodeBuffer += e.key;
❌ 3. Нет реакции на Enter
Даже когда буфер заполнялся:
"4829381234"
ничего не происходило.
Не было обработки:
if (e.key ==="Enter") {
// логика поиска
}
❌ 4. Обработчик создавался несколько раз
Самая неприятная ошибка.
React-компонент ререндерится → useEffect может сработать несколько раз.
В итоге:
1 скан → 2–3 добавления товара
Причина:
document.addEventListener("keydown", handler);
вызывался несколько раз.
✅ Решение
useEffect(() => {
consthandler= (e) => { ... };
document.addEventListener("keydown", handler);
return () => {
document.removeEventListener("keydown", handler);
};
}, [ ]);
Ключевое:
[ ] → выполнить один раз
removeEventListener → удалить обработчик
❌ 5. Потеря актуального каталога
Обработчик не видел актуального каталога.
Решение:
const catalogRef=useRef();
useEffect(() => {
catalogRef.current =localCatalog;
}, [localCatalog]);
И использовать:
catalogRef.current
5. Итоговая логика сканера
let barcodeBuffer="";
let lastKeyTime=0;
const handleKeyDown= (e) => {
const now=Date.now();
if (now - lastKeyTime>100) {
barcodeBuffer="";
}
lastKeyTime=now;
if (e.key ==="Enter") {
if (barcodeBuffer.length >=6) {
const product=catalogRef.current?.products?.find(p => p.barcode ===barcodeBuffer );
if (product) {
dispatch(addProduct(product));
}
}
barcodeBuffer="";
return;
}
if (e.key.length ===1) {
barcodeBuffer += e.key;
}
};
6. Как правильно отлаживать такие вещи
Очень важный вывод.
Шаг 1 — проверить backend
приходит ли запрос после сканирования?
Если да → проблема не в сканере
Если нет → идём на фронт
Шаг 2 — проверить фронт
Смотреть:
console.log(e.key)
Понимаем:
приходят ли вообще символы
Шаг 3 — проверить buffer
собирается ли строка
Шаг 4 — проверить Enter
срабатывает ли финальное событие
Шаг 5 — проверить реакцию на событие
dispatch(addProduct)
7. Главный вывод
Сканер — это не “устройство”, а:
поток клавиатурных событий
И если он “не работает” — почти всегда проблема:
в обработке этих событий на фронте
Финал
Этот кейс показал:
железо почти никогда не виновато
Если:
сканер не работает на разных терминалах
→ значит проблема:
в коде витрины