QRcode component in Svelte, featuring bind:this

I was just porting a hybrid mobile web app from Ionic+Angular to SvelteKit+Capacitor, and there was a qrcode generator page, where a number of qrcodes get generated in a sequence.

I wasn’t happy with the custom libraries so i opted to use the generate node-qrcode package, which supports the ESM import syntax. Some other libs expect you to import a script over the web, but i need something that will package up (Vite+capacitor).

The problem i had is that the example code uses a <canvas> element, which gets populated via DOM manipulation:

<!-- index.html -->
<html>
  <body>
    <canvas id="canvas"></canvas>
    <script src="bundle.js"></script>
  </body>
</html>
// index.js -> bundle.js
var QRCode = require('qrcode')
var canvas = document.getElementById('canvas')

QRCode.toCanvas(canvas, 'sample text', function (error) {
  if (error) console.error(error)
  console.log('success!');
})

My approach was to put the above code in a svelte component (Qrcode.svelte) which would do the work. Then its is called like so:

<!-- +page.svelte -->
<script lang="ts">
import Qrcode from '$components/Qrcode.svelte';
import { writable, type Writable } from 'svelte/store';

let tagIds: Writable<Array<string>> = writable(['101', '102', '103', '104']);
let qrcodeWidth = '160';
</script>

<div>
  <div>
    {#each $tagIds as tagId}
      <Qrcode value={tagId} width={qrcodeWidth} errorCorrection="M" />
      <div class="tagid-caption">{tagId}</div>
    {/each}
  </div>
</div>

Now the problem is that only one qrcode gets written:

The reason is that when svelte creates each component in the DOM, they will all have the same ID, which we definitely don’t want – each ID and code must be unique.

It turns out the “right way” to address a DOM element in a Svelte component is to use the bind:this syntax.

We create a variable for the canvas element, use bind:this={canvas} in the <canvas> html tag, and in onMount(), QRCode.toCanvas(canvas).

<!-- Qrcode.svelte -->
<script>
import QRCode from 'qrcode';
import { onMount } from 'svelte';

// https://stackoverflow.com/a/58015980/408747
// we use bind:this so this component can bind qrcode to the particular canvas in the DOM
// svelte will create unique DOM ids for each component
let canvas;

export let value = '';
export let width = '200';
export let errorCorrectionLevel = 'M';

const options = {
    errorCorrectionLevel,
    width,
};

onMount(() => {
    QRCode.toCanvas(canvas, value, options, function (error) {
        if (error) console.error(error);
    })
});

</script>

<canvas bind:this={canvas}></canvas>

Now we have canvas elements w/unique qrcodes, using a normal Svelte component and a vanilla ESM JavaScript QRcode library. 😊

Update: after posting, i just was made aware of a new svelte qrcode generator package – phippsytech/svelte-qrious. It uses the bind:this in the component definition, like i explained. But it’s an installable npm package, with a REPL.

Leave a Reply

Your email address will not be published. Required fields are marked *