やりたいこと
React.js (TypeScript) で、form の text フィールドの入力を、QR コードを読み込んで自動入力したい。form は、react-hook-form を使いたい。
QRコードマークをタップすると、カメラが立ち上がり、QRコードを認識したら text 情報をテキストフィールドにセットして、カメラが閉じる。カメラは、Modal を立ち上げるようにした。react-modal で。
QR Code Reader のパッケージ検討
- react-qr-reader (もしくは、素の jsQr)
- react-zxing
react-qr-reader は、バージョンが、3.0.0-beta-1 という斬新なバージョニングで、README のサンプルコードは動かないし、動かしてみたけど、読み込みの感度が微妙だったりと、私的にいまいちだったのに対し、react-zxing は、簡単にすんなり動き、感度もそこそこだったので、今回はそれを使うことにした。
react-zxing
Github の Usage に書いてある
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 すると、めちゃくちゃエラーが出る。
GENERATE_SOURCEMAP=false
私は、SOURCE MAP必要ないので、これで。
インストール等
$ 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
/** @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 にセットしている。
const [result, setResult] = useState<string>("");
...
<input type='text' {...register("code")} defaultValue={result} />
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 する。
$ 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が信用ならんとか、カメラアクセス許可しますかとかは、進んでもらうと、以下動画のような感じで、やりたいことが実現できる。
動画は、Google Pixel6 でやっているけど、iPhone でももちろんできるし、PC でもできる。でも、iPhone 14Pro との比較だと Pixel6 のほうが感度が抜群に良かった。iPhone でしばらく読み込めない場合は、横向きにしたり刺激を与えると反応する。なんちゃってSSLだから?明るさ?詳しい人いたら教えてください。
こういうことをやりたい人は少ないのか、あんまり情報がなかったり、バージョンが古いと新しいものとの互換性がなかったりしてましたが、いったん目的のものは出来たので、満足しました。おしまい。