9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactでQRコードリーダーを実装する

Last updated at Posted at 2022-05-08

ブロックチェーンSymbolのウォレットの開発をしています。Webウォレットの開発ということで、React向けのQRコードリーダーの実装を進めていたのですが、意外と時間がかかったので、メモも兼ねてやり方を掲載します。

image.png

パッケージ

react@18.0.0
@mui/material@5.6.4
@zxing/browser@0.0.7
@zxing/library@0.19.1

QRライブラリの選定について

当初Webで検索し一番に試したライブラリは以下ですが、こちらは私の技術スキルでは内部エラーを解消できず、断念しました。

代わりに今回 zxing というライブラリを利用しています。

実装

以下呼び出される側です。副作用フックの実装の通りアンマウント時のカメラ等の処理も停止している為、QRコードの読込完了と共にアンマウントする想定です。

reader.tsx
import { BrowserQRCodeReader } from "@zxing/browser";
import React, { FC, useEffect, useRef, useState } from "react";
import Result from "@zxing/library/esm/core/Result";
import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem/MenuItem";
import Button from "@mui/material/Button/Button";
import { useTheme } from "@mui/material/styles";
import Typography from "@mui/material/Typography/Typography";

type QrCodeReaderProps = {
  setOpen: React.Dispatch<React.SetStateAction<boolean>>;
  onRead: (result: Result) => void;
};

type CameraDeviceInfo = {
  id: string;
  name: string;
};

export const QrCodeReader: FC<QrCodeReaderProps> = ({ onRead, setOpen }) => {
  const theme = useTheme();
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const mountedRef = useRef<boolean>(false);
  const [devices, setDevices] = useState<CameraDeviceInfo[]>([]);
  const [currentCamera, setCurrentCamera] = useState<string | undefined>(undefined);

  const setDevicesList = async (): Promise<CameraDeviceInfo[]> => {
    const list = await BrowserQRCodeReader.listVideoInputDevices();
    const result: CameraDeviceInfo[] = [];
    for (const device of list) {
      result.push({ id: device.deviceId, name: device.label });
    }
    setDevices([...result]);
    return result;
  };

  useEffect(() => {
    mountedRef.current = true;
    const codeReader = new BrowserQRCodeReader(undefined, undefined);
    setDevicesList();
    codeReader.decodeFromVideoDevice(currentCamera, videoRef.current!, function (result, _, controls) {
      if (mountedRef.current === false) {
        controls.stop();
        return;
      }
      if (typeof result !== "undefined") {
        controls.stop();
        onRead(result);
      }
    });
    return function cleanup() {
      mountedRef.current = false;
    };
  }, [currentCamera]);


  return <div style={{
    position: "fixed",
    top: 0,
    left: 0,
    height: "100vh",
    width: "100vw",
    backgroundColor: theme.palette.background.default,
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    flexDirection: "column"
  }}>
    <Typography variant="h6" style={{ marginBottom: "1em", width: "90%", maxWidth: "1000px" }} fontWeight="bold" align="center">
      QRコードを読み込んでください
    </Typography>
    {
      devices.length !== 0 &&
      <Select
        value={currentCamera === undefined ? devices[0]?.id : currentCamera}
        onChange={e => { setCurrentCamera(e.target.value); }}
        style={{ width: "90%", maxWidth: "1000px" }}
      >
        {devices.map((device, index) => <MenuItem value={device.id} key={index.toString()} >{device.name}</MenuItem>)}
      </Select>
    }

    <video style={{ width: "90%", maxWidth: "1000px", borderRadius: "10px", marginTop: "1em", marginBottom: "1em" }} ref={videoRef} />
    <Button variant="outlined" color="primary" style={{ width: "90%", maxWidth: "1000px" }} onClick={() => setOpen(false)} size="large">
      STOP
    </Button>

  </div >;

};
index.tsx
export function Index(): JSX.Element {
  const [isOpenQRCamera, setIsOpenQRCamera] = useState<boolean>(false);
  const [qrResult, setQrResult] = useState<any>({});

  const clickOpenQrReader = () => {
    setIsOpenQRCamera(true);
  };

  return <Container maxWidth="md" style={{ paddingTop: "1em", paddingBottom: "10em" }}>
    <Button onClick={clickOpenQrReader}>test</Button>
    {isOpenQRCamera && <QrCodeReader onRead={e => {
      setIsOpenQRCamera(false);
      console.log(e);
    }} setOpen={setIsOpenQRCamera} />}
  </Container >;
}
9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?