From 84bdf899a4c02694edef4ba3520e029a79cca991 Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Wed, 3 Dec 2025 09:39:27 -0600 Subject: [PATCH 1/2] use img --- src/components/StyledQrCode.tsx | 59 +++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/components/StyledQrCode.tsx b/src/components/StyledQrCode.tsx index f429de176..d2c5921f8 100644 --- a/src/components/StyledQrCode.tsx +++ b/src/components/StyledQrCode.tsx @@ -1,15 +1,14 @@ -import React, { useEffect, useRef } from 'react'; +import { t } from '@lingui/core/macro'; import QRCodeStyling, { Options } from 'qr-code-styling'; +import React, { useCallback, useEffect, useRef } from 'react'; // Make all properties optional and add React-specific props type StyledQRCodeProps = Partial & { className?: string; - download?: boolean; }; const StyledQRCode: React.FC = ({ className = '', - download = false, type = 'svg', shape = 'square', width = 300, @@ -38,9 +37,36 @@ const StyledQRCode: React.FC = ({ }, ...rest }) => { - const ref = useRef(null); + const ref = useRef(null); const qrCode = useRef(null); + const updateImageSrc = useCallback(async () => { + if (!qrCode.current || !ref.current) return; + + try { + const rawData = await qrCode.current.getRawData('svg'); + if (rawData instanceof Blob) { + const dataUrl = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + if (ref.current) { + resolve(reader.result as string); + } else { + reject(new Error('Component unmounted')); + } + }; + reader.onerror = reject; + reader.readAsDataURL(rawData); + }); + if (ref.current) { + ref.current.src = dataUrl; + } + } + } catch (error) { + console.error('Failed to get QR code data:', error); + } + }, []); + useEffect(() => { if (!ref.current) return; @@ -59,14 +85,7 @@ const StyledQRCode: React.FC = ({ }; qrCode.current = new QRCodeStyling(options); - qrCode.current.append(ref.current); - - const currentRef = ref.current; - return () => { - if (currentRef) { - currentRef.innerHTML = ''; - } - }; + updateImageSrc(); }, [ type, shape, @@ -79,6 +98,7 @@ const StyledQRCode: React.FC = ({ dotsOptions, backgroundOptions, rest, + updateImageSrc, ]); useEffect(() => { @@ -99,6 +119,7 @@ const StyledQRCode: React.FC = ({ }; qrCode.current.update(updateOptions); + updateImageSrc(); }, [ type, shape, @@ -111,18 +132,12 @@ const StyledQRCode: React.FC = ({ dotsOptions, backgroundOptions, rest, + updateImageSrc, ]); - useEffect(() => { - if (download && qrCode.current) { - qrCode.current.download({ - extension: type === 'svg' ? 'svg' : 'png', - name: 'qr-code', - }); - } - }, [download, type]); - - return
; + return ( + {t`QR + ); }; export default StyledQRCode; From 17d09c713293850910e65c42043617f5799a3912 Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Wed, 3 Dec 2025 11:41:06 -0600 Subject: [PATCH 2/2] claude review --- src/components/StyledQrCode.tsx | 70 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/src/components/StyledQrCode.tsx b/src/components/StyledQrCode.tsx index d2c5921f8..5f471e947 100644 --- a/src/components/StyledQrCode.tsx +++ b/src/components/StyledQrCode.tsx @@ -39,31 +39,53 @@ const StyledQRCode: React.FC = ({ }) => { const ref = useRef(null); const qrCode = useRef(null); + const abortControllerRef = useRef(null); const updateImageSrc = useCallback(async () => { if (!qrCode.current || !ref.current) return; + // Cancel any pending operations + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + const signal = abortControllerRef.current.signal; + try { const rawData = await qrCode.current.getRawData('svg'); + if (signal.aborted || !ref.current) return; + if (rawData instanceof Blob) { const dataUrl = await new Promise((resolve, reject) => { + if (signal.aborted) { + reject(new Error('Aborted')); + return; + } + const reader = new FileReader(); reader.onloadend = () => { - if (ref.current) { - resolve(reader.result as string); + if (signal.aborted || !ref.current) { + reject(new Error('Component unmounted or aborted')); } else { - reject(new Error('Component unmounted')); + resolve(reader.result as string); } }; reader.onerror = reject; reader.readAsDataURL(rawData); }); - if (ref.current) { + + if (!signal.aborted && ref.current) { ref.current.src = dataUrl; } } } catch (error) { - console.error('Failed to get QR code data:', error); + if ( + error instanceof Error && + error.message !== 'Aborted' && + error.message !== 'Component unmounted or aborted' + ) { + console.error('Failed to get QR code data:', error); + } } }, []); @@ -86,40 +108,12 @@ const StyledQRCode: React.FC = ({ qrCode.current = new QRCodeStyling(options); updateImageSrc(); - }, [ - type, - shape, - width, - height, - margin, - data, - qrOptions, - imageOptions, - dotsOptions, - backgroundOptions, - rest, - updateImageSrc, - ]); - - useEffect(() => { - if (!qrCode.current) return; - const updateOptions: Partial = { - type, - shape, - width, - height, - margin, - data, - qrOptions, - imageOptions, - dotsOptions, - backgroundOptions, - ...rest, + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } }; - - qrCode.current.update(updateOptions); - updateImageSrc(); }, [ type, shape, @@ -133,6 +127,8 @@ const StyledQRCode: React.FC = ({ backgroundOptions, rest, updateImageSrc, + // Note: rest is intentionally omitted from dependencies to avoid infinite loops + // The spread operator in the options object will still include rest properties ]); return (