QR Scanner Hook with Simulated QR Codes
Implementation Example
tsx
import React from "react";
import useQRCodeReader from ".../useQRCodeReader";
const codes = ["QR1", "QR2", "QR3"];
const QRScannerExample: React.FC = () => {
const { qrCodeData, resetQRCodeData, QRCodeReader } = useQRCodeReader({
qrCodeParser: decodeQRCodeText, // Function to parse the scanned QR code text
simulatedQR: true, // Enable simulated QR codes. Hit Enter to cycle through the codes
qrCodes: codes, // Array of simulated QR codes
});
return (
<>
{/* Display a warning when the page is not focused */}
<QRCodeReader />
</>
);
};
export default QRScannerExample;
QR Scanner Hook definition
typescript
import { useEffect, useState, useCallback } from "react";
type ParserFunction= (text: string) => any
export type QRCodeReaderHookProps<T extends ParserFunction> = {
qrCodeParser: T
qrCodes?: string[];
throttleTime?: number;
simulatedQR?: boolean;
};
/**
* Custom hook to handle QR code reading functionality, including support for simulated QR codes.
*
* @param {function} qrCodeParser - Function to parse the scanned QR code text into the desired format.
* @param {number} [throttleTime=500] - Optional throttle time (in milliseconds) to limit the frequency of QR code scans.
* @param {boolean} [simulatedQR=false] - Optional flag to enable or disable the simulated QR code reader.
* @param {Array<string>} [qrCodes=[]] - Optional array of simulated QR codes for testing or development purposes.
*
* @returns {Object} An object containing:
* - `qrCodeData`: The most recently parsed QR code data.
* - `resetQRCodeData`: A function to reset the parsed QR code data to null.
* - `QRCodeReader`: A React component to display a warning when the page is not focused.
*
* @example
* const { QRCodeReader, resetQRCodeData, qrCodeData } = useQRCodeReader({
* qrCodeParser: parseQRCode,
* throttleTime: 300,
* simulatedQR: true,
* qrCodes: ['QR1', 'QR2'],
* });
*/
const useQRCodeReader = <T extends ParserFunction>({
qrCodeParser,
qrCodes = [],
simulatedQR = false,
throttleTime = 500,
}: QRCodeReaderHookProps<T>) => {
const [isFocused, setIsFocused] = useState(false);
const [qrCodeData, setQRCodeData] =
useState<ReturnType<typeof qrCodeParser> | null>(null);
const resetQRCodeData = useCallback(() => setQRCodeData(null), []);
const throttledOnRead = useCallback(
throttle((data: string) => {
const decodedData = qrCodeParser(data);
decodedData && setQRCodeData(decodedData);
}, throttleTime),
[qrCodeParser, throttleTime]
);
const readSimulatedQR = useSimulatedQRReader({
enabled: simulatedQR,
qrCodes,
});
// Handle QR Code reads
useEffect(() => {
let data = "";
const listenQRCodeRead = (e: KeyboardEvent) => {
if (e.key === "Enter") {
throttledOnRead(readSimulatedQR() ?? data);
data = "";
return;
}
data += e.key;
};
document.addEventListener("keypress", listenQRCodeRead);
return () => document.removeEventListener("keypress", listenQRCodeRead);
}, [throttledOnRead, readSimulatedQR]);
// Check if the document has focus so the USB scanner can read correctly
useEffect(() => {
const onFocus = () => setIsFocused(true);
const onBlur = () => setIsFocused(false);
setIsFocused(document.hasFocus());
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
}, []);
// Define the QRCodeReader component
const QRCodeReader = () => {
if (isFocused) return null;
return (
<div className="absolute bottom-10 left-10 z-50 w-fit rounded-lg border border-red bg-white p-10 text-red">
{"Page is not focused. QR code reader won't scan any data."}
</div>
);
};
return { qrCodeData, resetQRCodeData, QRCodeReader };
};
/**
* Throttle function to limit the frequency of a given function's execution.
*
* @param {function} func - The function to be throttled.
* @param {number} delay - The delay in milliseconds between function executions.
*
* @returns {function} A throttled version of the input function.
*/
export const throttle = <T extends any[]>(
func: (...args: T) => any,
delay: number
) => {
let wait = false;
return (...args: T) => {
if (!wait) {
func(...args);
wait = true;
setTimeout(() => {
wait = false;
}, delay);
}
};
};
interface SimulatedQRReaderProps {
enabled?: boolean;
qrCodes?: string[];
}
/**
* Custom hook to simulate QR code reading using a predefined list of QR codes.
*
* @param {boolean} [enabled=true] - Flag to enable or disable the simulated QR code reader.
* @param {Array<string>} [qrCodes=[]] - Array of simulated QR codes to be used by the reader.
*
* @returns {function} A function to simulate reading the next QR code from the list.
*/
export const useSimulatedQRReader = ({
enabled = true,
qrCodes = [],
}: SimulatedQRReaderProps) => {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
if (enabled && qrCodes.length === 0) {
console.error("No simulated QR codes provided.");
}
}, [qrCodes]);
const readSimulatedQR = () => {
if (!enabled) return null;
const currentCode = qrCodes[currentIndex];
const nextIndex = (currentIndex + 1) % qrCodes.length;
setCurrentIndex(nextIndex);
console.log("Reading simulated QR code", currentCode);
return currentCode;
};
return readSimulatedQR;
};
export default useQRCodeReader;