Template being repeated on data update / UI change

I have a template I display for different cards and sets of data on the screen, showing a QR Code. It should only show 1 time per element it relates to, which it does initially.

I have a on / off switch in the UI for the user to toggle, and when it’s toggled, the QR code gets repeated, so now there are 2 QR codes on the screen for the element stacked vertically.

I also have a timed event on the server that might update the screen reactively if certain data is changed, and when that happens the QR Code is also repeated for that element.

My code looks like this.

html template

<template name="clientCard">
    {{#if Template.subscriptionsReady}}
    <div class="col s12">
        <div class="card horizontal">
            <div class="card-image">
                <img src="/images/{{interfaceOS}}.png">
            </div>
            <div class="card-stacked">
                <div class="card-content">
                    <div class="row">
                        <div class="col s8">
                            <span class="card-title">{{interfaceName}}</span>
                            <div class="row">
                                <div class="col l4 m4 s6">
                                    <strong>IP:</strong>
                                </div>
                                <div class="col l8 m8 s6">
                                    {{interfaceIP}} : {{interfacePort}}
                                </div>
                            </div>
                            <div class="row">
                                <div class="col l4 m4 s6">
                                    <strong>DNS:</strong>
                                </div>
                                <div class="col l8 m8 s6">
                                    {{interfaceDNS}}
                                </div>
                            </div>
                            <div class="row">
                                <div class="col s12">
                                    <strong>Public Key:</strong> {{interfacePublicKey}}
                                </div>
                            </div>
                            <div class="row">
                                <div class="col l4 m4 s6">
                                    <strong>Check If Online:</strong>
                                </div>
                                <!-- <div class="col l8 m8 s6">
                                    {{#if $eq checkOnline true}}
                                        Yes
                                    {{else}}
                                        No
                                    {{/if}}
                                </div> -->

                                <div class="switch">
                                    <!-- Check if Online -->
                                    <label>
                                        No
                                        <input type="checkbox" id="{{_id}}" checked="{{#if $eq checkOnline true}}checked{{/if}}" >
                                        <span class="lever"></span>
                                        Yes
                                    </label>
                                </div>
                            </div>
                            <div class="row">
                                <div class="col l4 m4 s6">
                                    <strong>Client Status:</strong>
                                </div>
                                <div class="col l8 m8 s6">
                                    {{#if $eq status "online"}}
                                        <strong>Online</strong>
                                    {{else}}
                                        Offline
                                    {{/if}}
                                </div>
                            </div>
                        </div>
                        <div class="col s4">
                            <div id="QR{{_id}}">{{> qrcode text=clientQR size=150}}</div>
                        </div>
                    </div>
                </div>
                <div class="card-action">
                    <a href="#" id="downloadInterface">Download Interface File</a>
                    <a href="#" id="removeClient">Remove Client</a> 
                </div>
            </div>
        </div>
    </div>
    {{/if}}
</template>

With the offending section being:

<div class="col s4">
    <div id="QR{{_id}}">{{> qrcode text=clientQR size=150}}</div>
</div>

and my helper methods look like:

Template.clientCard.helpers({
    clientData: function() {
        return Interfaces.find({});
    },
    clientQR: function() {
        let interfaceId = this._id;
        let thisInterface;

        let myhost = location.hostname;

        let interfaceInfo = Interfaces.findOne({ _id: interfaceId });
        let serverInfo = ServerInfo.findOne({});

        let qrBlob = "[Interface]\nPrivateKey = " + interfaceInfo.interfacePrivateKey + "\nDNS = " + interfaceInfo.interfaceDNS + "\n" + "Address = " + interfaceInfo.interfaceIP + "/22," + interfaceInfo.interfaceIPv6 +"/112\n\n[Peer]\nPublicKey = " + serverInfo.publicKey + "\nEndpoint = " + myhost + ":" + interfaceInfo.interfacePort + "\nAllowedIPs = 0.0.0.0/0, ::/0";

        return qrBlob;
    },
});

Any thoughts on why this element is being repeated on data updates, and any way I can stop it from happening?

I’m using the panter:qrcode package for this functionality.

Additional Information:
The template appears to create a canvas tag with the height and width inside it, and that’s what’s being duplicated as data changes in the parent template.

As always, thanks for any help.

1 Like

Mixing reactive rendering with non reactive, e.g., manual manipulation of the dom can do this. I’ve seen it happen in weird places before, I can’t see anything obviously wrong here though.

1 Like

My gut feeling is that the package is generating new (albeit visually identical) images each time it’s used - does it write these into a MongoDB collection? Can you check?

Unfortunately, the GitHub link from atmosphere is dead, so I can’t look at the code without installing it myself.

It may also be worth looking at your offending section in the chrome web inspector. Check if the <div id="QRxxx"> has different ids for each image, which may indicate something in your code, rather than the package.

I believe it is the {{>qrcode}} invocation being reactive, because the clientQR helper is.

I agree.

:slight_smile:

Yes sir, I did the inspector route, and it doesn’t give it different IDs each time. Actually, it’s one div, with multiple Canvas tags inside it. I don’t believe it’s writing to a MongoDB, but something to look into.

I was thinking about this too @jamgold, and was thinking maybe I just need to bring up the QR code when I click a button on the card instead in a modal. So, I set that up, and it still showed multiple QR Codes after I changed data in the form. So, that tells me, since I was using a helper, that it does appear to be something about reactivity.

I think next I’ll try to use the event click itself to load the QR code with no helper, but just passing the values it needs, and see if it still happens.

Thanks for the help on this.

I took a look, that package just wraps the jquery qrcode - so 100% it is manually modifying the DOM. One option you might consider (it’s ugly) is in your clientQR helper, you could remove all canvases form within your QR div. This will have the effect of cleaning up old canvases before the new one is rendered.

A better option would be to somehow destroy the parent div each time. An even better option would be to ditch the package entirely, install your own version of jquery qrcode and then you have complete control. Here’s the code I was looking at: https://github.com/panter/meteor-qrcode

There really is no part in meteor-qrcode that does any cleanup of the DOM elements added by jquery-qrcode used by that meteor module.

I just created a small demo project with the jquery-qrcode npm module and just how @znewsham mentioned you need to destroy the canvases. Here is a snippet of code

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
  <div id="qrcode"></div>
</template>

And the necessary hooks

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import 'jquery-qrcode';
import './main.html';

Template.hello.onCreated(function helloOnCreated() {
  // counter starts at 0
  this.counter = new ReactiveVar(0);
  this.qrcode = null;
  this.code = new ReactiveVar('something');
});
Template.hello.onRendered(function(){
  const instance = this;
  window.hello = instance;
  instance.autorun(function(){
    if(instance.qrcode) instance.$('canvas', instance.qrcode).remove();
    instance.qrcode = instance.$('#qrcode').qrcode(instance.code.get());
  })
})

Now you can just go into the browser console and type

hello.code.set('some new code')

and you will see how it will replace the qrcode

1 Like

Why not use an npm package such as https://www.npmjs.com/package/qrcode

1 Like