8

I am trying to add a captcha to a LWC and the only way I have found was the hack to add it to an iframe with a URL of a static resource.

As part of that the iframe HTML is using postMessage to send the response back. When that happens I am trying to set the value of a @track decorated property to false so that a button is disabled.

However, it seems that this.disableSubmit is undefined in the callback. how do I get access to the LWC property in this use case?

TEMPLATE

<template>
     <iframe src={navigateTo} name="captchaFrame" onload={captchaLoaded} width="100%" height="100%" style="border-style: none;"></iframe>

     <lightning-button onclick={handleClick} type="submit" name="submit" disabled={disableSubmit} variant="brand" label="Submit" ></lightning-button>

</template>

JS

import { LightningElement, track, api } from 'lwc';
import pageUrl from '@salesforce/resourceUrl/reCaptchaForm';

export default class GoogleCapatcha extends LightningElement {
    @track navigateTo;
    @track disableSubmit = false;

    @api captchaSettings;

    connectedCallback() {
        this.navigateTo = pageUrl;
        this.disableSubmit = true;
        window.addEventListener("message", this.listenForMessage);
    }

    listenForMessage(message){
        if (message.data==="Unlock"){
            console.log(this.disableSubmit); ***This outputs undefined
            this.disableSubmit = false; **This does NOT enable the button

        }
    }
    //Used to test that if enabled that clicking will cause it to be disabled. Have to comment out the this.disableSubmit=true in the connectedCallback to test
    handleClick(event){
        event.preventDefault();
        console.log(this.disableSubmit); ***This outputs true
        this.disableSubmit = true; **This DOES disable the button
    }
}

Post Message Code in iFrame

parent.postMessage("Unlock", "https://xxxxx.force.com");

How do I get access to the disableSubmit property from the listenForMessage function

4 Answers 4

7

You have to remember that the this value changes depending on how the function is invoked. Use bind to bind the this value to the function and don't forget to remove the listener since you are adding it globally:

connectedCallback() {
  // Save a reference to the bound function since it has a different identity
  this._listenForMessage = this.listenForMessage.bind(this);
  window.addEventListener('message', this._listenForMessage);
}
disconnectedCallback() {
  window.removeEventListener('message', this._listenForMessage);
}
2
  • Accepted this answer as it was more complete and included the disconnected callback
    – Eric
    Commented Jan 4, 2021 at 7:34
  • 3
    IMPORTANT Don’t use addEventListener(eventName, this.handleNotification.bind(this)). It’s an anti-pattern because bind() returns a new function and thus the component can’t call removeEventListener() with the same function instance. Because the component can’t use the same function instance, the listener creates a memory leak. Please refer: developer.salesforce.com/docs/component-library/documentation/… Commented Jul 1, 2021 at 4:56
7

The answer from Eugene's while avoiding the leak it stills create a duplicate function by using the bind method and this is still an anti pattern described by Salesforce documentation.

What is done by Eugene is not to lose the reference to the function being used as the handler which allows you to remove it later.

So in order to avoid creating a new function you could create the handler as an arrow function which it will have the scope (this) already there by doing the following:

listenForMessage = (message) => {
 ...
}

connectedCallback() {
  window.addEventListener('message', this.listenForMessage);
}
disconnectedCallback() {
  window.removeEventListener('message', this.listenForMessage);
}

I hope it helps. Bests!

3
  • This solution worked for me. Thanks Hugo.
    – Keith C
    Commented Sep 1, 2021 at 17:23
  • This appears to be the correct approach, avoiding the bind, which is explicitly noted by the Dev documentation to avoid. This worked for me. Commented Jan 9, 2022 at 18:10
  • +1 this solution worked for me. this should be the top answer to avoid the anti-pattern outlined by SF documentation. Thanks Hugo! Commented Feb 11, 2022 at 0:02
3

I solved it by changing:

window.addEventListener("message", this.listenForMessage);

to

window.addEventListener("message", this.listenForMessage.bind(this));

I believe that is the correct way to do it.

0

I don't think you should use:

window.addEventListener("message", this.listenForMessage.bind(this));

This is an anti-pattern as it creates a memory leak as documented here.

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.events_handling

Instead use Eugene's answer which which avoids the leak by providing a disconnect method.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .