Easily scan QR and EAN codes in your React application. Exports a useZxing hook that streams camera video to a <video> element and decodes barcodes using barcode-detector (ZXing-C++ via WebAssembly).
Decoding relies on a WebAssembly module (zxing_reader.wasm). You can load it from a CDN (default), bundle it with your app, or host it yourself.
Do nothing extra. useZxing calls prepareWasm() internally, which loads the WASM file from jsDelivr. This works for most online apps.
To start loading WASM earlier (before the scanner mounts), call prepareWasm() in your app entry:
import { prepareWasm } from "react-zxing";
await prepareWasm();For offline use, strict CSP, or to avoid a CDN dependency, host zxing_reader.wasm yourself and pass its URL to prepareWasm.
With Vite, install zxing-wasm in your app and import the file URL:
import wasmUrl from "zxing-wasm/dist/reader/zxing_reader.wasm?url";
import { prepareWasm } from "react-zxing";
await prepareWasm({ wasmUrl });Alternatively, copy node_modules/zxing-wasm/dist/reader/zxing_reader.wasm to your static assets (for example public/zxing_reader.wasm) and pass a stable path:
await prepareWasm({ wasmUrl: "/zxing_reader.wasm" });You can also pass wasmUrl directly to useZxing instead of calling prepareWasm() yourself.
prepareWasm() is idempotent: repeated calls share the same initialization promise. Call it once with the URL you want before mounting scanners. A later call with a different wasmUrl rejects; if preparation fails, you can call again to retry. When you preload with a custom URL, useZxing can omit wasmUrl.
Camera access requires a secure context. localhost works, but LAN IPs like http://192.168.x.x do not — use HTTPS in development and production when testing on other devices.
import { useState } from "react";
import { useZxing } from "react-zxing";
export const BarcodeScanner = () => {
const [result, setResult] = useState("");
const { ref } = useZxing({
onDecodeResult(result) {
setResult(result.rawValue);
},
});
return (
<>
<video ref={ref} muted playsInline />
<p>
<span>Last result: </span>
<span>{result}</span>
</p>
</>
);
};Decode results are DetectedBarcode objects from the Barcode Detection API. Use result.rawValue for the scanned text and result.format for the symbology (e.g. "qr_code", "ean_13").
You can get the device ID from the MediaDevices API yourself, or use react-media-devices to list available devices:
import { useMediaDevices } from "react-media-devices";
import { useZxing } from "react-zxing";
const constraints: MediaStreamConstraints = {
video: true,
audio: false,
};
export const BarcodeScanner = () => {
const { devices } = useMediaDevices({ constraints });
const deviceId = devices?.[0]?.deviceId;
const { ref } = useZxing({
paused: !deviceId,
deviceId,
});
return <video ref={ref} muted playsInline />;
};Limit which symbologies are scanned with the formats option. Values follow the Barcode Detection API format names, plus convenience groups like "retail_codes", "linear_codes", and "any":
const { ref } = useZxing({
formats: ["retail_codes"],
onDecodeResult(result) {
console.log(result.format, result.rawValue);
},
});Control the torch via the torch property on the hook return value:
import { useZxing } from "react-zxing";
export const BarcodeScanner = () => {
const {
ref,
torch: { on, off, isOn, isAvailable },
} = useZxing();
return (
<>
<video ref={ref} muted playsInline />
{isAvailable ? (
<button type="button" onClick={() => (isOn ? off() : on())}>
{isOn ? "Turn off" : "Turn on"} torch
</button>
) : (
<strong>Unfortunately, torch is not available on this device.</strong>
)}
</>
);
};Torch support depends on the device exposing the torch constraint. Check isAvailable before showing torch controls.
For hard-to-read skewed labels, the scanner can retry failed frames at slight rotation angles. Enable the default angles with trySkew: true ([-20, -15, -10, -5, 5, 10, 15, 20]), or pass custom angles in degrees:
useZxing({ trySkew: [-15, 0, 15] });Each failed frame tries one angle from the list, rotating through them on subsequent attempts.
| Name | Type | Default | Description |
|---|---|---|---|
| onDecodeResult | function | Called when a barcode is decoded. Receives a DetectedBarcode . | |
| onDecodeError | function | Called when decoding throws an error. Empty frames (no barcode found) do not trigger this callback. | |
| onError | function | Called when camera or WASM setup fails, e.g. missing permissions or an insecure context. | |
| formats | BarcodeFormat[] | all formats | Barcode symbologies to scan. See barcode-detector for supported values. |
| wasmUrl | string | jsDelivr CDN |
URL of zxing_reader.wasm for self-hosting. Passed to
prepareWasm() before scanning starts.
|
| trySkew | boolean | number[] | false |
Retry decoding at rotation angles when a frame fails. Pass
true to use the default angles, or an array of angles in degrees.
|
| timeBetweenDecodingAttempts | number | 300 | Milliseconds to wait between decoding attempts. |
| constraints | MediaStreamConstraints | { video: { facingMode: 'environment' }, audio: false } |
Constraints passed to getUserMedia.
|
| deviceId | string | Explicit camera device ID to stream from. | |
| paused | boolean | false |
Stops the camera stream when true.
|
| Export | Description |
|---|---|
useZxing |
React hook for camera streaming and barcode scanning |
UseZxingOptions |
Options type for useZxing() |
prepareWasm |
Preload the WASM decoder; optional { wasmUrl } for self-hosting |
PrepareWasmOptions |
Options type for prepareWasm() |
BarcodeFormat |
Type for format filter values |
DetectedBarcode |
Type for decode results |
Version 3 replaces @zxing/library with barcode-detector (ZXing-C++ via WebAssembly). Update your app as follows:
| v2 | v3 |
|---|---|
result.getText() |
result.rawValue |
hints: Map<DecodeHintType, unknown> |
formats: BarcodeFormat[] |
onDecodeError(error: Exception) |
onDecodeError(error: unknown) |
| Skewed-frame retry always on | Opt in with trySkew |
Decode results are now DetectedBarcode objects. Use result.format for the symbology (for example "qr_code", "ean_13").
onDecodeError is only called when decoding throws. Empty frames no longer trigger it — v2 filtered NotFoundException, ChecksumException, and FormatException during continuous scanning; v3 treats a missing barcode as a normal empty frame.
For self-hosted or offline apps, add WASM setup with prepareWasm() or the wasmUrl option (see Setup above).
# Install dependencies
pnpm install
# Build the library
pnpm build
# Run unit tests
pnpm test
# Start the example (HTTPS enabled for camera access on LAN devices)
pnpm devThe example runs at https://localhost:5173.