top of page

Seamless Interaction: Boosting React Apps with Barcode and QR Code Scanners

By Nikos Rzewucki, Frontend Developer


Sometimes, when creating a frontend application for professionals, we encounter a UX problem where users must repeatedly input certain information to interact with the app. This issue can be prevalent in various use cases, such as retail, logistics, staff management, and more. Visual integrations, particularly QR codes and barcodes, can be a huge help in these scenarios. Physical scanning devices are usually an inexpensive solution that saves users a lot of time and allows for seamless integration. This article presents how to integrate these technologies into a React application. 

 

What are barcodes and QR codes?  

A barcode is a visual representation of data in the form of parallel lines of varying widths and spacings. It is one-dimensional (1D) and is used to store and read information such as product numbers, inventory codes, and other identifiers. Due to its popularity and standardization, they are easily understandable and acknowledged by most people. The most widespread standard is “Code 128” which can encode 128 ASCII characters in the most compressed form. 


A QR code (Quick Response code) is a type of two-dimensional barcode that can store a significant amount of information compared to traditional barcodes. It is characterized by its square shape and black-and-white patterns, which can be scanned using a camera-equipped device like a smartphone or a QR code reader. QR codes have much higher capacity and possibilities than barcodes and can hold data such as URLs, text, contact information, and more. 

For consistency, this article will focus on QR codes, however the solution can be applied to any code type. 


Scanning 

There are a variety of options to scan prepared QR code. Both a dedicated reader device and a built-in camera can be used, however the first one provides quicker and seamless operation.   Scanner, as a physical device is a simple input device, a “keyboard that types very fast”. It can operate both in wire and wireless way. 

 

How to make it work? 

Several issues need to be addressed to ensure functionality. The most significant issue is related to the input stream: determining when to start and stop reading. As QR codes can contain various types of content, it is essential to establish the start and end points of the input stream. Several methods are available to achieve this and choosing one should be preceded by a consideration of the use case and, most importantly, the format of the code. 


Options to determine the start of a reading:

  • Matching Prefix Pattern – If it is known that the code will have a specific pattern, or if a specific pattern can be enforced, this method is advisable. Examples include a URL (`HTTPS://<...>`), a number with a known prefix (`0000.<...>`), or a custom pattern (`myCode:<...>`). This allows the input stream to be identified as starting from the QR code without any additional actions required in advance. 

  • User Interaction – User can be asked to specify when they wish to scan a QR code, such as by clicking a button in the app that opens the input stream. Everything entered following this interaction is then considered part of the QR code. 

  • Read Everything – In applications that do not involve text input or other keyboard interactions, every text input can be treated as a QR code read. 

 

Options to determine the end of a reading: 

  • Matching Suffix Pattern – If the QR code consistently ends with a specific character, or if a special character can be designated to mark the end (e.g., `Name Surname^`), this method can be used. After reading this character or sequence of characters, the input stream will be closed. 

  • Matching Input Length – For QR codes of a fixed length, such as phone numbers, parcel numbers, or employee IDs, only a predefined number of characters may be read. This can also be combined with a matching prefix, where a given QR code type in the application always has a fixed length. 

  • Reading Timeout – Since most scanners read QR codes character by character with a few milliseconds delay between each, a fixed timeout can be defined for closing the input stream. For example, if no new character is provided within 200 milliseconds, the input stream will be closed. 

  • Scanner Newline – Many scanners are configured to end a QR code read with a newline character (`/n`, `Enter`). This character is not printed in the code itself but is added by the scanner during the reading process. This is a useful option to consider if the input is a single-line string. 

 

Another aspect to consider, depending on the use case, is which HTML element should be focused. In some cases, the best user experience would involve a separate component for handling scanning upon clicking. In such cases, it must be ensured that typing events do not bubble up and disable propagation. If occasional scanning is desired while the application is in operation, the window element is likely the best choice. 

 

Example 

Consider an application designed for parcel tracking using QR codes. These QR codes have the format of a URL with a unique identifier of variable length, and it should be possible to scan them seamlessly at any time while using the application. 

For a React application, a custom hook is well-suited for this purpose: 

export const useDebouncedQRListener = () => { }; 

The first task is to handle typing events. This can be accomplished by using the `keydown` event, so the following can be added to the custom hook: 

useEffect(() => { 
	window.addEventListener("keydown", handleKeyDown); 

	return () => { 
		window.removeEventListener("keydown", handleKeyDown); 
	}; 
}, [handleKeyDown]); 

Next, two states are needed: the first to maintain the current buffer, which acts as a stack for storing read characters, and the second to store and return the combined input value, which will be returned by the hook. The buffer can be a simple string; however, in the discussed use case, the input value should be comparable by reference. To ensure that the hook returns a value even when the same QR code is scanned twice in succession, the input should be set as an object. This way, it will be compared by reference and will be considered changed by React every time a new value is created. The other option would be to reset the value and return empty string after setting an input with value. 

const [buffer, setBuffer] = useState(""); 
const [input, setInput] = useState({ qrCode: "" }); 
 ... 
 
return input; 

Next, a handler for `keydown` is needed, which was previously used in `useEffect`

const handleKeyDown = useCallback( 
	(eventKeyboardEvent) => { 
		setBuffer((prev) => prev event.key); 
	}, []); 

After setting up the buffer, the input needs to be set according to the specific use case outlined earlier. For the discussed example, a buffer reading timeout will be used: if there is a 200 ms delay between keystrokes, it will be considered that the QR input has ended. This can be achieved using a debouncing technique. Debouncing can be implemented manually or by using an external library, such as `use-debounce`. The `useDebounceCallback` function delays the execution of a given function for a specified time and cancels it when the new call happens within that time. As a result, the input will only be set when the QR reader stops receiving new characters. 

const debouncedSetInput = useDebouncedCallback((buffer) => setInput({ qrCodebuffer }); 
}, 200); 

Next, `debouncedSetInput` should be called every time the buffer changes. There is no need to set an input after the buffer is cleared, unless the hook is intended to return an empty string between scans. 

useEffect(() => if (buffer.length) { 
		debouncedSetInput(buffer); 
	} 
}, [buffer, debouncedSetInput]); 

Finally, the buffer clear is needed, after the input has been changed: 

useEffect(() => { 
	setBuffer(""); 
}, [input]); 

 

Let’s use the following code in app parent component to test the behavior. 

const scannerOutput = useDebouncedQRListener(); 

useEffect(() => { 
	const { qrCode } = scannerOutputqrCode && window.alert(`You have scanned QR code: ${qrCode}`); 
}, [scannerOutput]); 

Depending on the QR code and scanner settings, there may be situations where the 'Shift' key is added at the beginning of the input stream. To address this issue, certain special keys can be excluded. 

const EXCLUDED_KEYS = ["Shift", "Control", "Alt", "Meta", "Clear", "CapsLock"];  

const handleKeyDown = useCallback( 
	(eventKeyboardEvent) => { 
		if (EXCLUDED_KEYS.includes(event.key)) { 
			return; 
		} 
		setBuffer((prev) => prev event.key); 
	}, [debouncedSetInput]); 

 

To enhance the stability of the solution, especially when scanning or typing quickly, additional validation can be added. In this example, only URL-based QR codes are expected to be scanned. Therefore, any input that is not a URL can be ignored by extending the `debouncedSetInput` method. 

enum QRType URL = "HTTPS://", 
}  

...  

const debouncedSetInput = useDebouncedCallback((buffer) => {
	if (buffer.toUpperCase().startsWith(QRType.URL)) {
   		setInput({ qrCode: buffer });
    	} else {
    		setBuffer('');
    }
}, 200);

Moreover, the scanner can be configured to add a newline at the end of each scanned QR code. When a newline is received, there is no need to wait for the debounce period; the setter can be executed immediately. The `useDebouncedCallback` function has a `flush()` method for such cases. This condition can be added to the `handleKeyDown` function. 

const FLUSH_KEYS = ["Enter"]; 

if (FLUSH_KEYS.includes(event.key)) { 
	debouncedSetInput.flush(); 
	return; 
} 

The complete code looks like this: 

import { useCallback, useEffect, useState } from "react"; 
import { useDebouncedCallback } from "use-debounce"; 
 
const EXCLUDED_KEYS = ["Shift", "Control", "Alt", "Meta", "Clear", "CapsLock"]; 
const FLUSH_KEYS = ["Enter"]; 
 
enum QRType { 
	URL = "HTTPS://"
};
 
export const useDebouncedQRListener = () => { 
const [buffer, setBuffer] = useState(""); 
const [input, setInput] = useState({ qrCode: "" }); 
 
 const debouncedSetInput = useDebouncedCallback((buffer) => {
    	if (buffer.toUpperCase().startsWith(QRType.URL)) {
    		setInput({ qrCode: buffer });
    } else {
     	setBuffer('');
    }
  }, 200);
 
const handleKeyDown = useCallback( 
	(event: KeyboardEvent) => { 
		if (EXCLUDED_KEYS.includes(event.key)) { 
			return; 
		} 
		if (FLUSH_KEYS.includes(event.key)) { 
			debouncedSetInput.flush(); 
			return; 
		} 
		setBuffer((prev) => prev + event.key); 
}, [debouncedSetInput]);  

useEffect(() => { 
	if (buffer.length) { 
		debouncedSetInput(buffer); 
	} 
}, [buffer, debouncedSetInput]); 

useEffect(() => { 
	setBuffer(""); 
}, [input]); 

useEffect(() => { 
	window.addEventListener("keydown", handleKeyDown); 
	
	return () => { 
		window.removeEventListener("keydown", handleKeyDown); 
	}; 
}, [handleKeyDown]); 

return input; 
};

 The final effect will look like this: 



As demonstrated, integrating QR code scanning into applications can be achieved quickly and easily, significantly enhancing user experience. By utilizing a custom hook in React, managing typing events, and implementing debouncing, QR codes can be captured and processed effortlessly. This not only simpliclickingfies application interaction but also adds valuable functionality for seamless user engagement. 

 

Komentarai


bottom of page