---
name: scanning
description: "Use when a feature reads barcodes or QR codes, from a handheld laser scanner (keystrokes) via useScanDetection or from the device camera via @yudiel/react-qr-scanner."
metadata:
  type: convention
  library: wcz-layout
---

## Rules

- Laser scanners type the code as fast keystrokes. Catch them with `useScanDetection` from
  `wcz-layout/hooks`, never a hidden input or an own `keydown` listener. It listens on
  `document`, so nothing needs focus.
- `useScanDetection({ onComplete, ... })` options (defaults): `minLength` (1),
  `averageWaitTime` (50 ms between keys, slower input counts as typing), `timeToEvaluate`
  (100 ms), `startCharacter` (`[]`), `endCharacter` (`["Enter", "Escape"]`), `onError`,
  `container` (`document`), `ignoreIfFocusOn`, `preventDefault` / `stopPropagation` (`false`),
  `enabled` (`true`). Set `minLength` to the shortest valid code; use `enabled: false` while
  scans must be ignored.
- Camera scanning always uses `Scanner` from `@yudiel/react-qr-scanner` (install if missing),
  never another QR/barcode library. Rear camera, finder and torch are on by default. Always set:
  - `constraints={{ width: { ideal: 1920 }, height: { ideal: 1080 } }}`: Full HD, the default
    720p misses small and dense codes.
  - `formats`: only the symbologies the feature expects (`qr_code`, `code_128`, `ean_13`, …).
    All formats are scanned by default, which is slower and misreads more.
  - `onError`: alert `error.message` (permission denied, no camera, camera in use).
- Open the camera in a custom dialog that resolves with the first scanned value (see `dialogs`).
  It needs HTTPS or `localhost`; the component is SSR-safe.
- Where both scanners may be used, keep `useScanDetection` on the page, add a camera button,
  and send both results to the same handler.

## Examples

```tsx
import { Dialog, DialogContent, DialogTitle } from "@mui/material";
import { Scanner } from "@yudiel/react-qr-scanner";
import type { DialogProps } from "wcz-layout/hooks";
import { useDialogs, useScanDetection, useTranslation } from "wcz-layout/hooks";

// laser scanner
useScanDetection({ onComplete: handleCode, minLength: 8 });

// camera button
const { open } = useDialogs();
const code = await open(ScanDialog);
if (code) await handleCode(code);

// camera dialog
export const ScanDialog = ({ open, onClose }: DialogProps<undefined, string | undefined>) => {
  const { t } = useTranslation();
  const { alert } = useDialogs();

  return (
    <Dialog fullWidth open={open} onClose={() => onClose(undefined)}>
      <DialogTitle>{t("Scan")}</DialogTitle>
      <DialogContent>
        <Scanner
          constraints={{ width: { ideal: 1920 }, height: { ideal: 1080 } }}
          formats={["qr_code", "code_128"]}
          onScan={([code]) => onClose(code.rawValue)}
          onError={(error) => alert(error.message)}
        />
      </DialogContent>
    </Dialog>
  );
};
```
