How to Build a QR Code Scanner in React Native Web

In the previous article, we run a QR code scanner in React-Native-WebView on Android and iOS. React Native can also run in a web browser with React Native Web, which uses React DOM to accurately render React Native compatible JavaScript code in a web page.

In this article, we are going to make the QR code scanner project in the previous article work in the web browser, as well.

Install Dependencies

In order to make the Expo project run in the browser, we need to install the following dependencies as prompted by the cli tool.

npx expo install react-native-web@~0.18.10 react-dom@18.2.0 @expo/webpack-config@^18.0.1

Then, we can run the project in the web:

npm run web

Create a QR Code Scanner Component for Web

In the previous article, we created a QR code scanner component with React-Native-WebView which only works on mobile platforms. We need to create a QR code scanner for the web.

  1. Create a new file named QRCodeScannerWeb.js with the following template.

    import { useState, useRef } from "react";
    export default function QRCodeScanner(props) {
      const [status,setStatus] = useState("");
      const container = useRef(null);
      return (
        <div>
          <div>{status}</div>
          <div ref={container} className="scanner" style={{position:"absolute",top:0,left:0,width:"100%",height:"100%"}}></div>
        </div>
      );
    }
    
  2. Load Dynamsoft Barcode Reader and Dynamsoft Camera Enhancer from CDN and initialize instances of them. Dynamsoft Barcode Reader requires a license. You can apply for one here.

    const enhancer = useRef(null);
    const reader = useRef(null);
    useEffect(() => {
      const loadLibrariesAndInit = async () => {
        setStatus("Initializing...");
        await loadLibrary("https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.3.4/dist/dce.js","text/javascript");
        await loadLibrary("https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.20/dist/dbr.js","text/javascript");
        await init();
        setStatus("");
      }
      const init = async () => {
        Dynamsoft.DBR.BarcodeScanner.license = 'DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=='; // one-day trial
        reader.current = await Dynamsoft.DBR.BarcodeScanner.createInstance();
        enhancer.current = await Dynamsoft.DCE.CameraEnhancer.createInstance();
        await enhancer.current.setUIElement(Dynamsoft.DCE.CameraEnhancer.defaultUIElementURL);
        container.current.appendChild(enhancer.current.getUIElement()); //bind the viewer
      }
      loadLibrariesAndInit();
    },[]);
      
    const loadLibrary = (src,type,id,data) => {
      return new Promise(function (resolve, reject) {
        let scriptEle = document.createElement("script");
        scriptEle.setAttribute("type", type);
        scriptEle.setAttribute("src", src);
        if (id) {
          scriptEle.id = id;
        }
        if (data) {
          for (let key in data) {
            scriptEle.setAttribute(key, data[key]);
          }
        }
        document.body.appendChild(scriptEle);
        scriptEle.addEventListener("load", () => {
          console.log(src+" loaded")
          resolve(true);
        });
        scriptEle.addEventListener("error", (ev) => {
          console.log("Error on loading "+src, ev);
          reject(ev);
        });
      });
    }
    
  3. Start the camera after the component is mounted and set an interval to scan barcodes from the camera frames when the camera is opened.

    useEffect(() => {
      const loadLibrariesAndInit = async () => {
        setStatus("Initializing...");
        await loadLibrary("https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.3.4/dist/dce.js","text/javascript");
        await loadLibrary("https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.20/dist/dbr.js","text/javascript");
        await init();
        setStatus("");
      }
      const init = async () => {
        //...
        enhancer.current.on("played", (playCallbackInfo) => {
          console.log("camera started");
          startProcessingLoop();
        });
        enhancer.current.on("cameraClose", playCallBackInfo => {
          if (props.onClosed) {
            props.onClosed();
          }
          stopScan();
        });
        //...
        startScan();
      }
      loadLibrariesAndInit();
    },[]);
    
    const startScan = () => {
      if (!enhancer.current || !reader.current) {
        alert("Please wait for the initialization of Dynamsoft Barcode Reader");
        return;
      }
      enhancer.current.open(true); //start the camera
    }
         
    const stopScan = () => {
      stopProcessingLoop();
      enhancer.current.close(true);
    }
    
    const startProcessingLoop = () => {
      stopProcessingLoop();
      interval.current = setInterval(captureAndDecode,100); // read barcodes
    }
    
    const stopProcessingLoop = () => {
      if (interval.current) {
        clearInterval(interval.current);
        interval.current = undefined;
      }
      processing.current = false;
    }
    
    async function captureAndDecode() {
      if (!enhancer.current || !reader.current) {
        return
      }
      if (enhancer.current.isOpen() === false) {
        return;
      }
      if (processing.current == true) {
        return;
      }
      processing.current = true; // set processing to true so that the next frame will be skipped if the processing has not completed.
      let frame = enhancer.current.getFrame();
      if (frame) {  
        let results = await reader.current.decode(frame);
        if (results.length > 0) {
          stopScan();
          if (props.onScanned) {
            props.onScanned(results); //pass the barcode results to the parenet
          }
        }
        processing.current = false;
      }
    };
    

Dynamically Import the QR Code Scanner for Different Platforms

Open App.js and dynamically import the QR code scanner for different platforms.

let QRCodeScanner;
const loadComponent = async () => {
  if (Platform.OS === "web") {
    const module = await import("./QRCodeScannerWeb");
    QRCodeScanner = module.default;
  }else{
    const module = await import("./QRCodeScanner");
    QRCodeScanner = module.default;
  }
}
loadComponent();

All right, we’ve now finished making the QR code scanner work in a web browser. Here is a link to the demo hosted on Netlify: https://main--moonlit-queijadas-b14d28.netlify.app/

Source Code

Get the source code of the demo to have a try:

https://github.com/tony-xlh/QR-Code-Scanner-React-Native-WebView