Handheld scanners emulate a keyboard. They type the code and press Enter, faster than any human, and they do not wait for your UI to finish thinking. Most warehouse software is built as if a person were typing carefully.
The failure modes are all timing
The form is not focused yet. The previous scan triggered a navigation, focus landed nowhere, and the next scan types into the void. The item is never recorded and the picker does not notice until the count is short.
The lookup is synchronous. Each scan hits the server before the field clears, so the operator sees a frozen field and scans again. Now you have a duplicate.
Debounce is applied. Someone added a 300ms debounce meant for search-as-you-type. The scanner fires a full code in under 50ms, the debounce swallows the trailing characters, and the code is truncated.
What actually works
Keep focus pinned. A single always-focused input, refocused on every blur, on every route change, and after every submit. It is unglamorous and it eliminates the largest class of lost scans.
Accept on Enter, not on keystroke. Scanners terminate with a carriage return. Buffer characters until you see it, then process the whole string at once. No debounce, no per-character work.
Validate the format locally before any network call. Check length and check digit in the browser. A malformed code should be rejected in a millisecond with a distinct sound, not after a round trip.
Optimistic by default
The picker should never wait for the server to confirm a scan. Append the line locally, show it immediately, and reconcile in the background. If the server disagrees — item not on this order, already picked — surface it as a correction on the line rather than as a modal that steals focus and loses the next three scans.
Sound is the interface
A picker is looking at a shelf, not a screen. Distinct tones for accepted, rejected, and complete carry more than any visual state, and they work at arm’s length. One good tone and one bad tone is more usable than a colour-coded table nobody is looking at.