React.js (TypeScript) で、QR コードを読み込んでフォームにセットする

## やりたいこと

React.js (TypeScript) で、form の text フィールドの入力を、QR コードを読み込んで自動入力したい。form は、react-hook-form を使いたい。

![Screen Shot 2022-10-31 at 15.21.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/28a2e421-ca04-968b-d7b9-788766fe3c18.png)

QRコードマークをタップすると、カメラが立ち上がり、QRコードを認識したら text 情報をテキストフィールドにセットして、カメラが閉じる。カメラは、Modal を立ち上げるようにした。react-modal で。

## QR Code Reader のパッケージ検討

- [react-qr-reader](https://www.npmjs.com/package/react-qr-reader) (もしくは、素の [jsQr](https://www.npmjs.com/package/jsqr)）
- [react-zxing](https://www.npmjs.com/package/react-zxing)

react-qr-reader は、バージョンが、3.0.0-beta-1 という斬新なバージョニングで、README のサンプルコードは動かないし、動かしてみたけど、読み込みの感度が微妙だったりと、私的にいまいちだったのに対し、react-zxing は、簡単にすんなり動き、感度もそこそこだったので、今回はそれを使うことにした。

## react-zxing

Github の Usage に書いてある

```javascript
import { useState } from "react";
import { useZxing } from "react-zxing";

export const BarcodeScanner = () => {
  const [result, setResult] = useState("");
  const { ref } = useZxing({
    onResult(result) {
      setResult(result.getText());
    },
  });

  return (
    <>
      <video ref={ref} />
      <p>
        <span>Last result:</span>
        <span>{result}</span>
      </p>
    </>
  );
};
```

これをそのまま使えば、QR コードの読み取りは完了する。TypeScript への対応も、```useState<string>("")``` にしとくかくらいで、ほとんどやることはない。react-qr-reader みたいな、インカム起動するのかよ！みたいなのもない。簡単すぎる！！

が、これで yarn start すると、めちゃくちゃエラーが出る。

```.env
GENERATE_SOURCEMAP=false
```

私は、SOURCE MAP必要ないので、これで。

## インストール等

```bash
$ yarn create react-app qr_reader --template typescript
$ cd qr_reader
$ yarn add react-hook-form react-modal react-zxing @emotion/react
$ yarn add @types/react-modal --dev
```

QRコードのアイコンは、iconmonstr から使わせていただきました。

## 完成

```
src
├── components
│   ├── ScanModal.tsx // モダル（App.tsx から呼ばれる）
│   └── QrReader.tsx  // QR コードリーダー（ScanModal から呼ばれる）
└── App.tsx // Form
```
とする。手抜きだけど。


### App.tsx

```typescript:App.tsx
/** @jsxImportSource @emotion/react */
import React, { useState } from "react";
import { css } from "@emotion/react";
import { useForm } from "react-hook-form";
import { ScanModal } from "./components/ScanModal";

function App() {
  // Modal の開閉
  const [isOpen, setIsOpen] = useState<boolean>(false);
  const onClickToggle = () => {
    setIsOpen(!isOpen);
  };
  // 読み込んだ QR コードのテキスト情報を格納
  const [result, setResult] = useState<string>("");

  // form の設定・処理
  const { handleSubmit, register } = useForm();
  const onSubmitForm = (formData: any) => {
    console.log(formData);
  };

  const container = css`...`;
  const formStyle = css`...`;
  const qrButton = css`...`;

  return (
    <div css={container}>
      <form onSubmit={handleSubmit(onSubmitForm)} css={formStyle}>
        <input type='text' {...register("code")} defaultValue={result} />
        <button type='submit'>submit</button>
      </form>

      <button onClick={onClickToggle} css={qrButton}>
        <img src='/qr-code.png' alt='scan qr code' />
      </button>
      <ScanModal
        isOpen={isOpen}
        onRequestClose={() => setIsOpen(false)}
        setResult={setResult}
      />
    </div>
  );
}

export default App;
```

QRコードリーダーで読み込んだテキストは、result に格納される。これを text フィールドの defaultValue にセットしている。

```typescript
  const [result, setResult] = useState<string>("");
...
        <input type='text' {...register("code")} defaultValue={result} />
```

### QrReader.tsx

```typescript:QrReader.tsx
import { useZxing } from "react-zxing";

type Props = {
  setResult: React.Dispatch<React.SetStateAction<string>>;
  onRequestClose: () => void;
};

const QrReader = ({ setResult, onRequestClose }: Props) => {
  const { ref } = useZxing({
    onResult(result) {
      setResult(result.getText());
      onRequestClose();
    },
  });

  return <video ref={ref} />;
};

export default QrReader;
```

サンプルコードの setStateAction が外だしになっているのと、onResult に react-modal を閉じるために渡している onRequestClose が追加されている。何か情報が入ってきたら、setResult して、モダルを閉じる。

よく、QRコードを認識したら、認識した QR コードを border で囲うみたいな演出があるけど、今回はすぐ閉じちゃうので検討外。やるなら、result.getResultPoints() などを利用すればできるものと思われる。


### ScanModal 

ただの react-modal なので、省略。
かっこよく装飾したいとかあれば、デザインを当てればよいと思われる。


## 実機確認

スマホでカメラにアクセスするには、https になっている必要があるので、https で yarn start する。

```bash
$ HTTPS=true yarn start
```

こうすると、

```
Compiled successfully!

You can now view qr_reader in the browser.

  Local:            https://localhost:3000
  On Your Network:  https://192.168.1.13:3000

Note that the development build is not optimized.
To create a production build, use yarn build.

webpack compiled successfully
No issues found.
```

と、出てくるので、

```
  On Your Network:  https://192.168.1.13:3000
```

今回だと、https://192.168.1.13:3000 に同じネットワーク上であれば、違うデバイスからもアクセスできる。
SSLが信用ならんとか、カメラアクセス許可しますかとかは、進んでもらうと、以下動画のような感じで、やりたいことが実現できる。


https://www.youtube.com/shorts/floApjs3o_4

動画は、Google Pixel6 でやっているけど、iPhone でももちろんできるし、PC でもできる。でも、iPhone 14Pro との比較だと Pixel6 のほうが感度が抜群に良かった。iPhone でしばらく読み込めない場合は、横向きにしたり刺激を与えると反応する。なんちゃってSSLだから？明るさ？詳しい人いたら教えてください。


こういうことをやりたい人は少ないのか、あんまり情報がなかったり、バージョンが古いと新しいものとの互換性がなかったりしてましたが、いったん目的のものは出来たので、満足しました。おしまい。