How to download fancy QR Codes with React
Tags: react, javascript, webdev, tutorial
Published: 2023-01-08 10:48:45

Generating QR codes on the web is easy. There are lots of libraries out there who help us do it. One of them is [qrcode.react](https://www.npmjs.com/package/qrcode.react) which we will be using for the purpose of this tutorial.

## Code to download a plain QR Code
We're rendering a `<canvas>` element in the DOM using `QRCodeCanvas` supplied by `qrcode.react`. We can convert it to a `png` using JavaScript and then download it as a file.
```tsx
// QRCodeDownload.tsx
import React from "react"
import { QRCodeCanvas } from "qrcode.react"

import Button from "components/Button"

const QRCodeDownload = () => {
  const url = "https://anshsaini.com"

  const downloadQRCode = () => {
    const canvas = document.querySelector("#qrcode-canvas") as HTMLCanvasElement
    if (!canvas) throw new Error("<canvas> not found in the DOM")

    const pngUrl = canvas
      .toDataURL("image/png")
      .replace("image/png", "image/octet-stream")
    const downloadLink = document.createElement("a")
    downloadLink.href = pngUrl
    downloadLink.download = "QR code.png"
    document.body.appendChild(downloadLink)
    downloadLink.click()
    document.body.removeChild(downloadLink)
  }

  return (
    <div className="p-3">
      <QRCodeCanvas id="qrcode-canvas" level="H" size={300} value={url} />
      <div className="my-5">
        <Button onClick={downloadQRCode}>Download QR Code</Button>
      </div>
    </div>
  )
}

export default QRCodeDownload
```
![QR Code downloaded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kuu7f96ktpksg5a4sdv6.png)

Voila✨ The QR Code is now saved in the file system as an image.

## Taking it a step further
No one wants to share a QR code on its own. Its ugly. We want a nice UI surrounding the QR code, giving it some context. Maybe something like this:
![Fancy QR Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb4o9tc6jad4vfuctig0.png)

Let's a create a wrapper component which will handle the styling. I'm using `react-jss` for styling but of course you can use whatever you want and style your QR Code however you like.
```tsx
// QRCodeTemplate.tsx
import React, { FC, PropsWithChildren } from "react"

import DEV_RAINBOW_LOGO from "assets/qr_code/dev-rainbow.png"
import DEV_RAINBOW_BG from "assets/qr_code/dev-rainbow-bg.png"
import { createUseStyles } from "react-jss"

import Typography from "components/Typography"

const ICON_SIZE = 100

const useStyles = createUseStyles(theme => ({
  root: {
    backgroundImage: `url('${DEV_RAINBOW_BG}')`,
    maxWidth: 1060,
    maxHeight: 1521,
    borderRadius: "32px",
    border: "5px solid #EFEFF7",
    padding: theme.spacing(11),
  },
  logo: {
    boxShadow: "9px 3px 20px 1px rgb(0 0 0 / 10%)",
    height: 150,
    width: 150,
    borderRadius: 8,
  },
  icon: {
    borderRadius: 8,
    width: ICON_SIZE,
    height: ICON_SIZE,
    position: "absolute",
    // Need to offset the values due to `excavate: true` in qrcode.react
    top: `calc(50% - ${ICON_SIZE / 2 + 1}px)`,
    // Need to offset the values due to `excavate: true` in qrcode.react
    left: `calc(50% - ${ICON_SIZE / 2 - 5}px)`,
  },
  qrContainer: {
    position: "relative",
    backgroundColor: "#EFEFF7",
    borderRadius: "56px",
    margin: theme.spacing(8, 0),
    padding: theme.spacing(4),
  },
  qrInner: {
    backgroundColor: "white",
    borderRadius: "32px",
    padding: 90,
  },
  referredBy: {
    fontStyle: "normal",
    fontWeight: "600",
    fontSize: "24px",
    lineHeight: "29px",
    letterSpacing: "0.1em",
  },
}))

const QRCodeTemplate: FC<PropsWithChildren> = ({ children }) => {
  const classes = useStyles()
  return (
    <div className={classes.root} id="fancy-qr-code">
      <img alt="logo" className={classes.logo} src={DEV_RAINBOW_LOGO} />
      <Typography className="mt-7" variant="title1">
        To register, scan the QR Code.
      </Typography>

      <div className={classes.qrContainer}>
        <img
          alt="icon"
          className={classes.icon}
          height={ICON_SIZE}
          src={DEV_RAINBOW_LOGO}
          width={ICON_SIZE}
        />
        <div className={classes.qrInner}>{children}</div>
      </div>
      <Typography className={classes.referredBy} variant="emphasised">
        REFERRED BY
      </Typography>
      <Typography variant="largeTitle">Ansh Saini</Typography>
    </div>
  )
}

export default QRCodeTemplate
```
We can now pass our QR Code as children to the `QRCodeTemplate`

```tsx
<QRCodeTemplate>
  <QRCodeCanvas
    // Passing image here doesn't render when we convert html to canvas. So, we handle it manually.
    // We are only using this prop for `excavate: true` use case.
    imageSettings={{
      src: "",
      x: undefined,
      y: undefined,
      height: 80,
      width: 80,
      excavate: true,
    }}
    level="H"
    size={640}
    value={url}
  />
</QRCodeTemplate>
```

Earlier we only had a canvas element in the DOM so we could directly convert it to a `png`. But now, our DOM includes HTML elements like `<div>`, `<img>` etc. for styling. So we need to parse our DOM as `canvas` and then convert it into a `png`. There's a libary which helps us do just that. [html2canvas](https://html2canvas.hertzen.com/). We modify our `downloadQRCode` function such that first we get snapshot of our DOM as a `canvas` then we convert that into a `png`.

## Final code 
```tsx
// QRCodeTemplate.tsx
import React from "react"

import html2canvas from "html2canvas"
import { QRCodeCanvas } from "qrcode.react"

import Button from "components/Button"
import QRCodeTemplate from "components/dashboard/QRCodeTemplate"

const QRCodeDownload = () => {
  const url = "https://anshsaini.com"

  const getCanvas = () => {
    const qr = document.getElementById("fancy-qr-code")
    if (!qr) return

    return html2canvas(qr, {
      onclone: snapshot => {
        const qrElement = snapshot.getElementById("fancy-qr-code")
        if (!qrElement) return
        // Make element visible for cloning
        qrElement.style.display = "block"
      },
    })
  }

  const downloadQRCode = async () => {
    const canvas = await getCanvas()
    if (!canvas) throw new Error("<canvas> not found in DOM")

    const pngUrl = canvas
      .toDataURL("image/png")
      .replace("image/png", "image/octet-stream")
    const downloadLink = document.createElement("a")
    downloadLink.href = pngUrl
    downloadLink.download = "QR code.png"
    document.body.appendChild(downloadLink)
    downloadLink.click()
    document.body.removeChild(downloadLink)
  }

  return (
    <div className="p-3">
      <QRCodeTemplate>
        <QRCodeCanvas
          // Passing image here doesn't render when we convert html to canvas. So, we handle it manually.
          // We are only using this prop for `excavate: true` use case.
          imageSettings={{
            src: "",
            x: undefined,
            y: undefined,
            height: 80,
            width: 80,
            excavate: true,
          }}
          level="H"
          size={640}
          value={url}
        />
      </QRCodeTemplate>
      <div className="my-5">
        <Button onClick={downloadQRCode}>Download QR Code</Button>
      </div>
    </div>
  )
}

export default QRCodeDownload
```

## The Result
We have a beautiful QR code saved as a `png`. And with the power of React, we can create as many styles as we want.
![Fancy QR Code downloaded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qy22fyjzjhuu85lkecg.png)