npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

use-barcode-scanner

v0.1.4

Published

React hooks for barcode scanning: a HID keyboard-wedge scanner and a camera decoder (Quagga2), sharing a dedupe engine that filters decoder noise without losing genuine repeat scans.

Readme

use-barcode-scanner

React hooks for scanning barcodes from two common inputs:

  • useBarcodeScanner — a HID keyboard-wedge scanner (USB/Bluetooth). These scanners type the code as fast keystrokes ending in Enter; this hook tells that apart from a human typing.
  • useBarcodeCamera — a phone/tablet camera, decoded with Quagga2, restricted to a centered region of interest so multiple visible barcodes don't all trigger a scan.

Both report scans through the same onScan(code: string) callback, so the rest of your app (looking up a product, adding a cart line, whatever) doesn't need to know which input produced the code.

Camera scanning is built on a small, framework-agnostic dedupe engine (createScanSession / registerDetection / registerMiss) that solves the actual hard problem: telling a genuine repeat scan (the same product scanned three times in a row) apart from decoder noise (a barcode misread for one frame, or briefly unreadable due to autofocus/motion blur) — without a fixed cooldown that would either block real repeats or let noise through. It's exported directly if you want to drive a decoder of your own.

Install

npm install use-barcode-scanner

react is a required peer dependency. @ericblade/quagga2 is an optional peer dependency — only install it if you use useBarcodeCamera:

npm install @ericblade/quagga2

useBarcodeScanner — keyboard-wedge scanner

import { useBarcodeScanner } from "use-barcode-scanner";

function Register() {
	useBarcodeScanner({
		enabled: true,
		onScan: (code) => {
			// look up `code` in your catalog, add it to the cart, etc.
		},
	});

	return <div>...</div>;
}

Listens on window, so it works regardless of what element has focus. Digits are read from event.code (Digit0–Digit9, Numpad0–Numpad9), not event.key — on a non-US layout (e.g. French AZERTY) the OS translates event.key into symbols, but event.code still names the physical key.

Options:

| Option | Default | Meaning | | --- | --- | --- | | enabled | — | Set to false to stop listening (e.g. while a dialog is open). | | onScan | — | Called with the scanned code once a fast-enough digit burst ends in Enter. | | maxIntervalMs | 100 | Max time between keystrokes to still count as the same scan. | | minLength | 5 | Minimum digit count for Enter to be treated as ending a scan. |

useBarcodeCamera — camera scanner

import { useBarcodeCamera } from "use-barcode-scanner";

function Viewfinder() {
	const { containerRef, status, scanArea } = useBarcodeCamera({
		enabled: true,
		onScan: (code) => {
			// same as above
		},
	});

	return <div ref={containerRef} style={{ position: "relative" }} />;
}

status is one of "idle" | "starting" | "running" | "permission-denied" | "no-camera" | "error" — render your own messaging around it.

Decoding is restricted to a centered band (DEFAULT_SCAN_AREA, also exported) covering 80% of the width and 25% of the height — a band, not a square, because an EAN-13 needs its full width in frame to decode. Draw your own visible frame over containerRef at those same coordinates if you want to show it to the user (color, border, whatever — this package draws no UI); Quagga's own border-draw isn't used here since it requires locate: false, which trades away the locating step this hook relies on for robust decoding.

By default only EAN-13 is read (DEFAULT_READERS) — the format this hook has actually been tuned and tested against, on real devices, across many rounds of fixes (see scan-session.ts's dedupe tuning). Pass readers to read others; it's forwarded to Quagga as-is (any format string or reader config Quagga itself accepts works here), and it replaces the default rather than adding to it — include "ean_reader" yourself if you want it alongside another format.

Other formats are supported, not validated. Nothing in this hook or in the dedupe engine assumes EAN-13 specifically — registerDetection/registerMiss treat the code as an opaque string, and the scan-area geometry applies to any 1D barcode. But the noise patterns a decoder produces (misread frequency, how long a code stays readable while held steady) can differ by format, and the defaults above were tuned watching EAN-13 on real hardware. If you enable another reader, treat dedupeWindowMs / missingGraceMs / codeChangeConfirmMs as a starting point to re-validate against your own real-device testing, not a guarantee.

Every knob that affects behavior is configurable:

useBarcodeCamera({
	enabled: true,
	onScan: (code) => { /* ... */ },

	// Merged over DEFAULT_SCAN_AREA — pass only the sides you want to change.
	scanArea: { top: "30%", bottom: "30%" }, // a taller band, e.g. for Code 128

	// Replaces DEFAULT_READERS (["ean_reader"]) entirely.
	readers: ["ean_reader", "code_128_reader"],

	// scan-session tuning (see below) — all optional, shown here with their defaults.
	dedupeWindowMs: 8000,
	missingGraceMs: 700,
	codeChangeConfirmMs: 150,
});

scanArea in the returned value is always the effective area (defaults merged with your override) — read it back instead of hardcoding your overlay's coordinates a second time, so they can never drift apart from what's actually being decoded.

Dedupe engine

import { createScanSession, registerDetection, registerMiss } from "use-barcode-scanner";

let session = createScanSession({
	dedupeWindowMs: 8000, // optional, shown with its default
	missingGraceMs: 700, // optional, shown with its default
	codeChangeConfirmMs: 150, // optional, shown with its default
});

// on every decoded frame:
const result = registerDetection(session, code, Date.now());
session = result.session;
if (result.accept) {
	/* handle the scan */
}

// on every frame where nothing decoded:
session = registerMiss(session, Date.now());

useBarcodeCamera already wires this up for you — reach for it directly only if you're driving a different decoder.

| Option | Default | Meaning | | --- | --- | --- | | dedupeWindowMs | 8000 | Safety-net ceiling, not the routine dedupe path — a barcode never removed from view re-accepts after this long regardless, in case registerMiss never fires. Real dedupe of a still-in-frame code is missingGraceMs. | | missingGraceMs | 700 | How long a code must be continuously missing before the same code counts as a new scan again. Raise it if your decoder is noisier (more single-frame misses on a motionless barcode); lower it to detect a genuine item swap faster. | | codeChangeConfirmMs | 150 | How long a different code must persist before it replaces the one currently tracked. Filters out single-frame misreads (garbage that still passes the barcode format's checksum) so they can't interrupt dedupe of the real code. |

License

MIT