← All @molecule/* packages · App templates

@molecule/app-feature-barcode-scanner-react

Feature · feature-barcode-scanner · App (browser) · v1.0.1 · Apache-2.0

Browser barcode scanner React component using BarcodeDetector with @zxing/library fallback — for warehouse, inventory, and grocery apps

npm install @molecule/app-feature-barcode-scanner-react

npm · Source on GitHub

How it works

@molecule/app-feature-barcode-scanner-react is a ready-made feature-barcode-scanner feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.

import { BarcodeScanner } from '@molecule/app-feature-barcode-scanner-react'

function ScanPanel() {
  return (
    <BarcodeScanner
      formats={['ean_13', 'upc_a']}
      onScan={({ format, value }) => addLineItem(value)}
      onError={(err) => showToast(err.message)}
    />
  )
}

Works with: @molecule/app-react, @molecule/app-ui

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Browser barcode scanner React component.

Acquires a rear-facing camera via getUserMedia({ video: { facingMode: 'environment' } }) and decodes frames using the W3C BarcodeDetector API where available (Chromium / WebView), falling back to @zxing/library for Safari / Firefox. Designed for warehouse-fulfillment, inventory- management, and grocery-delivery flagship apps.

Exports <BarcodeScanner>, the BarcodeFormat / BarcodeScanResult / BarcodeScannerError shapes, the DEFAULT_FORMATS constant, the buildZxingHints helper (W3C formats → zxing POSSIBLE_FORMATS hint), and the __setBarcodeDetectorOverride / __setZxingLoaderOverride test injection points.

Quick Start

import { BarcodeScanner } from '@molecule/app-feature-barcode-scanner-react'

function ScanPanel() {
  return (
    <BarcodeScanner
      formats={['ean_13', 'upc_a']}
      onScan={({ format, value }) => addLineItem(value)}
      onError={(err) => showToast(err.message)}
    />
  )
}

Type

feature

Installation

npm install @molecule/app-feature-barcode-scanner-react @molecule/app-react @molecule/app-ui @zxing/library react
npm install -D @types/react

API

Interfaces

BarcodeScannerError

Error shape passed to onError.

interface BarcodeScannerError {
  /** Stable machine-readable error code. */
  code: BarcodeScannerErrorCode
  /** Localized human-readable message. */
  message: string
  /** Original underlying error, if any. */
  cause?: unknown
}

BarcodeScannerProps

Props for <BarcodeScanner>.

interface BarcodeScannerProps {
  /**
   * Symbologies the scanner should accept. Defaults to the four most
   * common retail / logistics codes:
   * `['ean_13', 'upc_a', 'code_128', 'qr_code']`.
   */
  formats?: BarcodeFormat[]
  /** Fired with the decoded result on every successful scan. */
  onScan: (result: BarcodeScanResult) => void
  /** Fired when camera acquisition or detection fails. */
  onError?: (error: BarcodeScannerError) => void
  /**
   * When `true`, keeps scanning after each detection. Identical
   * consecutive reads are deduped only for a cooldown window
   * (`dedupeMs`) — after that window the SAME code can be scanned and
   * re-emitted again (e.g. adding two of the same item on purpose), and
   * a DIFFERENT code always emits immediately. When `false` (default),
   * stops the camera and the detection loop on the first successful scan.
   */
  continuous?: boolean
  /**
   * Polling interval, in milliseconds, between detector frames.
   * Defaults to `200`. Lower values increase responsiveness at higher
   * CPU cost.
   */
  scanIntervalMs?: number
  /**
   * Dedupe cooldown, in milliseconds. After a value is emitted, the
   * SAME value is suppressed for this long before it may be emitted
   * again (a different value is never suppressed). Prevents one physical
   * scan from firing `onScan` on every frame while still allowing a
   * deliberate re-scan of the same code. Defaults to `1500` (1.5s).
   */
  dedupeMs?: number
  /** Pixel width hint passed as the camera constraint. Defaults to 640. */
  width?: number
  /** Pixel height hint passed as the camera constraint. Defaults to 480. */
  height?: number
  /** Extra classes merged onto the root element. */
  className?: string
}

BarcodeScanResult

Result emitted from a successful scan.

interface BarcodeScanResult {
  /** Format of the detected symbology (e.g. `'ean_13'`). */
  format: BarcodeFormat | string
  /** Raw decoded value as produced by the underlying detector. */
  value: string
}

ZxingReader

Minimal subset of BrowserMultiFormatReader we depend on.

interface ZxingReader {
  /**
   * Decode a single frame from a `<video>` element. Returns
   * `{ text, format }` when a barcode is detected, otherwise `null` or
   * throws a `NotFoundException` (caller treats both as "no match").
   */
  decodeOnceFromVideoElement(video: HTMLVideoElement): Promise<{ text: string; format?: string }>
  /** Stop any internal scanning loop and release decoders. */
  reset(): void
}

Types

BarcodeFormat

Supported barcode/symbology formats. Mirrors the W3C Shape Detection BarcodeFormat enum so values can be passed straight through to the native BarcodeDetector constructor when present. The @zxing/library fallback maps this list onto zxing's DecodeHintType.POSSIBLE_FORMATS hint (see {@link buildZxingHints}), so the fallback reader is constrained to the same symbologies.

type BarcodeFormat =
  | 'aztec'
  | 'code_128'
  | 'code_39'
  | 'code_93'
  | 'codabar'
  | 'data_matrix'
  | 'ean_8'
  | 'ean_13'
  | 'itf'
  | 'pdf417'
  | 'qr_code'
  | 'upc_a'
  | 'upc_e'

BarcodeScannerErrorCode

Reasons the scanner can fail at runtime — surfaced via onError and via i18n-keyed status messages on the rendered overlay.

type BarcodeScannerErrorCode =
  /** `getUserMedia()` rejected with `NotAllowedError` or `SecurityError`. */
  | 'permission_denied'
  /** `getUserMedia()` rejected with `NotFoundError` (no camera attached). */
  | 'no_camera'
  /** Browser does not expose `navigator.mediaDevices.getUserMedia`. */
  | 'unsupported'
  /** A detector instance threw while decoding a frame. */
  | 'detector_failure'
  /** The fallback `@zxing/library` import failed (offline / blocked). */
  | 'fallback_unavailable'

ZxingLoader

Loader function returning a @zxing/library-compatible reader, constrained to the requested formats. Indirection lets us pin to a small subset of the surface area we actually depend on and lets tests stub the fallback path.

type ZxingLoader = (formats: BarcodeFormat[]) => Promise<ZxingReader>

Functions

__setBarcodeDetectorOverride(ctor)

Override the BarcodeDetector constructor used by the next mounted scanner. Pass null to force the fallback path even when the native API is present. Pass undefined to revert to the browser default.

function __setBarcodeDetectorOverride(ctor: BarcodeDetectorConstructor | null | undefined): void
  • ctor — Constructor stub or null/undefined.

__setZxingLoaderOverride(loader)

Override the @zxing/library loader used by the next mounted scanner. Pass null to force the loader to fail (simulating an offline bundle). Pass undefined to revert to the real dynamic import.

function __setZxingLoaderOverride(loader: ZxingLoader | null | undefined): void
  • loader — Loader stub or null/undefined.

BarcodeScanner(props)

Browser-side barcode scanner. Acquires a rear-facing camera via navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }), then drives a detection loop using the native BarcodeDetector API when available and falling back to @zxing/library otherwise.

The component is purely presentational on top of the camera stream — it renders a <video> plus a small status overlay routed through the companion locale bond and getClassMap(). Detected results are delivered to the caller through onScan; failures through onError. The camera stream and detection loop are torn down on unmount and on every prop change that affects acquisition.

function BarcodeScanner(props: BarcodeScannerProps): JSX.Element
  • props — Component props.

Returns: The scanner element.

buildZxingHints(formats, zxingBarcodeFormat, decodeHintType)

Translate the requested W3C barcode formats into a @zxing/library decode-hint map keyed by DecodeHintType.POSSIBLE_FORMATS, so the fallback reader is constrained to (and optimized for) exactly those symbologies instead of decoding every format it knows.

The zxing enum objects are passed in rather than imported at module scope so the heavy @zxing/library bundle stays lazily loaded — it is only pulled in on the fallback path.

function buildZxingHints(
  formats: BarcodeFormat[],
  zxingBarcodeFormat: Record<string, number>,
  decodeHintType: Record<string, number>,
): Map<number, number[]> | null
  • formats — The requested W3C symbologies.
  • zxingBarcodeFormat — zxing's BarcodeFormat enum object.
  • decodeHintType — zxing's DecodeHintType enum object.

Returns: A hint map for the reader constructor, or null when no requested format maps to a known zxing format (caller passes no hints, leaving the reader unconstrained).

Constants

DEFAULT_FORMATS

Default formats accepted when the caller doesn't pass formats.

const DEFAULT_FORMATS: BarcodeFormat[]

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-react ^1.0.1
  • @molecule/app-ui ^1.0.1
  • react ^18.0.0 || ^19.0.0

Runtime Dependencies

  • @molecule/app-react
  • @molecule/app-ui
  • @zxing/library
  • react

Camera access requires a SECURE CONTEXT — HTTPS or localhost — and a user permission grant. On plain http (or when the user denies), the component stays on its localized error overlay and fires onError with 'unsupported' / 'permission_denied'; there is nothing to retry until the context or permission changes.

The formats prop constrains BOTH detection paths: the native BarcodeDetector gets them directly, and the @zxing/library fallback (Safari / Firefox) maps them onto zxing's POSSIBLE_FORMATS decode hint so it too scans only the requested symbologies. The fallback still reports format: 'unknown' in its results — filter on result.value shape if the symbology matters cross-browser.

Identical values are deduped for a cooldown window (dedupeMs, default 1.5s), not forever: in continuous mode the SAME code can be re-scanned and re-emitted once the window elapses (e.g. adding two of the same item on purpose), and a DIFFERENT code always emits immediately. Set dedupeMs to tune the window.

All user-visible text routes through the companion locale bond @molecule/app-locales-feature-barcode-scanner. Styling routes through getClassMap() from @molecule/app-ui — no Tailwind utility class names appear in this package.

Translations

Translation strings are provided by @molecule/app-locales-feature-barcode-scanner.