Skip to content

Add a onComplete callback prop to <QRCodeCanvas> component #388

@josharens

Description

@josharens

I have a use case where I want users to be able to click a link to download a QR code shown via the <QRCodeCanvas> component. My first attempt was to get a data URL from the canvas via toDataURL() in a ref callback, and set that as the href on a link.

function MyApp() {
  const [ dataUrl, setDataUrl ] = useState('');
  
  const canvasRefCallback = useCallback((canvas) => {
    if (!canvas) {
      return;
    }

    setDataUrl(canvas.toDataURL());
  }, []);

  return (
    <div>
      <QRCodeCanvas
        ref={canvasRefCallback}
        value={window.location.href}
      />

      {dataUrl &&
        <a href={dataUrl} download="qrcode">Download QR Code</a>
      }
    </div>
  );
}

This unfortunately results in a transparent PNG because the <canvas> element is rendered and the ref callback is invoked before the useEffect() that draws the QR code into the canvas has run.

As a workaround, I'm having users click on a button first where I then create a link dynamically and call .click() on it to initiate the download. This is fine, but it's a little silly making users click on a button, so that I can create a link and call .click() on it, when the users themselves could just click on that link.

function MyApp() {
  const canvasRef = useRef();
  const downloadLinkRef = useRef();

  const onDownloadQRCode = useCallback(() => {
    const canvas = canvasRef.current;
    const donwloadLink = downloadLinkRef.current;

    if (!canvas || !downloadLink) {
      return;
    }

    downloadLink.href = canvas.toDataURL();
    downloadLink.download = 'qrcode';
    downloadLink.click();
  }, []);

  return (
    <div>
      <QRCodeCanvas
        ref={canvasRef}
        value={window.location.href}
      />

      <button onClick={onDownloadQRCode}>Download QR code</button>

      <a hidden ref={downloadLinkRef} />
    </div>
  );
}

Since drawing to the canvas is synchronous, I think the first example could work if <QRCodeCanvas> took a onComplete callback prop that it called at the end of the useEffect() after drawing the QR code has been completed.

function MyApp() {
  const [ dataUrl, setDataUrl ] = useState('');
  const canvasRef = useRef();
  
  const onDrawQRCodeComplete = useCallback(() => {
    const canvas = canvasRef.current;

    if (!canvas) {
      return;
    }

    setDataUrl(canvas.toDataURL());
  }, []);

  return (
    <div>
      <QRCodeCanvas
        onComplete={onDrawQRCodeComplete}
        ref={canvasRef}
        value={window.location.href}
      />

      {dataUrl &&
        <a href={dataUrl} download="qrcode">Download QR Code</a>
      }
    </div>
  );
}

I'd be happy to open a PR if this is something everyone is open to.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions