0

I have some of what I think it some fairly basic code to use a js library in a lightning web component and update to display a value once loaded from the callback that the JS library uses to load data.

here is my html

 <lightning-card title="HelloWorld" icon-name="custom:custom14" split-managed="true" >
     <div class="slds-m-around_medium" id="SplitCard">
         <p>test</p>
     </div>

 </lightning-card>
</template>

and here is my js


 import { LightningElement, api } from 'lwc';

// download the split SDK and put it into a static resource called splitsdk
// also ensure that you have added *.split.io to the content security policy
import splitsdk from '@salesforce/resourceUrl/splitsdk';
import { loadStyle, loadScript } from 'lightning/platformResourceLoader';


export default class HelloWorld extends LightningElement {

   renderedCallback() {      
    loadScript(this, splitsdk).then(() => {
        // your code with calls to the JS library
        var factory = splitio({ 
            core: {
              authorizationKey: '....', // your sdk key
              // key represents your internal user id, or the account id that 
              // the user belongs to. 
              // This could also be a cookie you generate for anonymous users
              key: 'key'
            }
          });
          // And get the client instance you'll use
          var client = factory.client();
          client.on(client.Event.SDK_READY, function() {
            console.log('SDK_READY')
             let treatment = client.getTreatment("demo_split");
            console.log('treatment = '+treatment)
            let card = this.template.querySelector("div");
            console.log(card)
            console.log(card.innerHTML)
            card.innerHTML = `<p>Treatment: ${treatment}</p>`;
                                
          });

    });
}
  


   

}

}

I'm able to get the console to log out the treatment value - which is great. This shows the SDK is working. However it doesn't seem like I'm able to update the html in the helloWorld component with it.

If there is an easier way I'm all ears. The example from SF showed using a changehandler and a template string like this

  changeHandler(event) {
    this.greeting = event.target.value;
  }

However I did some testing and couldn't get that to work with the code from the nested callback.

4 Answers 4

0

Instead of saving a reference to this, you could use either an arrow function, as Phil said, or you could bind the value of this to the original function.

The issue

client.on(client.Event.SDK_READY, function() {
    console.log('SDK_READY')
    let treatment = client.getTreatment("demo_split");
    console.log('treatment = '+treatment)
    let card = this.template.querySelector("div");
    console.log(card)
    console.log(card.innerHTML)
    card.innerHTML = `<p>Treatment: ${treatment}</p>`;
});

Here you defined a function with function keyword, so it has its own this, that's why this.template is undefined.

First Solution

Arrow functions, instead, use the this value of the enclosing execution context. Docs:

Until arrow functions, every new function defined its own this value (a new object in the case of a constructor, undefined in strict mode function calls, the base object if the function is called as an "object method", etc.)
[...]
An arrow function does not have its own this; the this value of the enclosing execution context is used.

So a solution would be using an arrow function:

client.on(client.Event.SDK_READY, () => {
    console.log('SDK_READY')
    let treatment = client.getTreatment("demo_split");
    console.log('treatment = '+treatment)
    let card = this.template.querySelector("div");
    console.log(card)
    console.log(card.innerHTML)
    card.innerHTML = `<p>Treatment: ${treatment}</p>`;
});

In this case this.template would not be undefined.

Second Solution

You could also use bind():

The bind() method creates a new function that, when called, has its this keyword set to the provided value

client.on(client.Event.SDK_READY, function() {
    console.log('SDK_READY')
    let treatment = client.getTreatment("demo_split");
    console.log('treatment = '+treatment)
    let card = this.template.querySelector("div");
    console.log(card)
    console.log(card.innerHTML)
    card.innerHTML = `<p>Treatment: ${treatment}</p>`;
}.bind(this)); // bind the value of this
1
  • Thanks - I see now how arrow functions would work. I think that's definitely a better solution than what I came up with by saving a reference to this
    – Josh Klein
    Commented Mar 2, 2023 at 13:20
1

Ok I got it working - this is some really PITA JS trickery - but basically it's the old game of scoping this correctly.

The answer is to use const that=this; in order to properly allow the correct this to traverse the callback tree.

here is the updated JS code

import { LightningElement, api } from 'lwc';

// download the split SDK and put it into a static resource called splitsdk
// also ensure that you have added *.split.io to the content security policy
import splitsdk from '@salesforce/resourceUrl/splitsdk';
import { loadStyle, loadScript } from 'lightning/platformResourceLoader';


export default class HelloWorld extends LightningElement {
   treatment = "new"
   treatmentNotLoaded= true;
   
   renderedCallback() {      
    loadScript(this, splitsdk).then(() => {
        // your code with calls to the JS library
        const that=this;
        var factory = splitio({ 
            core: {
              authorizationKey: 'SDK_KEY', // your sdk key
              // key represents your internal user id, or the account id that 
              // the user belongs to. 
              // This could also be a cookie you generate for anonymous users
              key: 'key'
            }
          });
          // And get the client instance you'll use
          var client = factory.client();
          client.on(client.Event.SDK_READY, function() {
            console.log('SDK_READY')
            let treatment = client.getTreatment("demo_split");
            console.log('treatment = '+treatment);
            that.treatment=treatment;        
            that.treatmentNotLoaded=false;
          });

    });
}
}

here is the updated HTML

<template>
<template lwc:if={treatmentNotLoaded}>
    <lightning-card  title="HelloWorld" icon-name="custom:custom14" split-managed="true" >
        <div class="slds-m-around_medium" >
            <p></p>
        </div>
   </lightning-card>
  </template>
<template lwc:else>
    <lightning-card title="HelloWorld" icon-name="custom:custom14" split-managed="true" >
        <div class="slds-m-around_medium" >
            <p>Treatment={treatment}</p>
        </div>
   </lightning-card>
  </template>
</template>
4
  • 1
    Specifically which callback? Your answer should illustrate the specific change applied in the context of the question code. Are you not able to use arrow functions to avoid this problem?
    – Phil W
    Commented Mar 2, 2023 at 5:58
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Mar 2, 2023 at 6:07
  • 1
    yes, thank you- added more code to the answer to show what I did. Not sure how arrow functions would help here but open to understanding what I'm missing.
    – Josh Klein
    Commented Mar 2, 2023 at 6:09
  • I liked @RubenDG 's answer with the arrow functions better than mine. I have accepted that solution
    – Josh Klein
    Commented Mar 2, 2023 at 14:02
0

If you're just looking to update and render the data that is returned from the callout then you don't need to inject the HTML as you're doing, you should be able to simply update an attribute in your JavaScript and reference that in your HTML, which is what the example you've shown is doing.

For this, the HTML would look like this:

 <lightning-card title="HelloWorld" icon-name="custom:custom14" split-managed="true" >
     <div class="slds-m-around_medium" id="SplitCard">
         <p>{treatment}</p>
     </div>
</lightning-card>

and in your JavaScript you'd define the treatment attribute (outside of any of your methods) and update it with the value from your response using:

this.treatment = treatment;

Typically when loading something like this, I'd recommend using an attribute to let your component react to the response being loaded, and use this to render the value only when required. You can achieve this with another attribute, something like treatmentLoad which you default to false when declared and then set it to true immediately after the treatment attribute is set. This can then be used in an lwc:if statement to show or hide the content that has just been loaded.

5
  • Thanks - however this didn't seem to work. I declare treatment at the beginning of the function right before the renderedCallback() definition it doesn't seem to ever update. The hardcoded initial value I set shows but it never changes once the treatment is logged out.
    – Josh Klein
    Commented Mar 2, 2023 at 5:15
  • btw I did add the lwc:if and it seems to work pretty well. However I'm still stuck that the console.log shows the value properly but the html doesn't update
    – Josh Klein
    Commented Mar 2, 2023 at 5:40
  • What happens if you log this.treatment after setting it? Just curious Commented Mar 2, 2023 at 5:43
  • 1
    it would just do nothing. btw I figured it out. Had to set const that=this at the top level right at the loadScript callback, then everything works.
    – Josh Klein
    Commented Mar 2, 2023 at 5:45
  • Awesome! Thanks for posting your solution, helps others (and me!) In the future. Commented Mar 2, 2023 at 6:30
0

Add lwc:dom="manual" to your div.

4
  • This didn't work either. It seems that the selector isn't working at all. I wonder if it's actually an issue where this within the SDK_READY callback is the wrong object
    – Josh Klein
    Commented Mar 2, 2023 at 5:22
  • Interesting. So the console.logs that you have for the card and card.innerHTML aren't showing anything? Commented Mar 2, 2023 at 5:27
  • Yeah it seems really weird. I added some additional console.log messages and the here0 gets logged to the console but here does not. Which definitely points to something odd with the querySelector? (sorry the formatting is bad) console.log('treatment = '+treatment); console.log('here0'); let card = this.template.querySelector("div"); console.log('here'); console.log(card); console.log(card.innerHTML);
    – Josh Klein
    Commented Mar 2, 2023 at 5:35
  • Salesforce LWC's implementation is very poorly designed. They prevent you from seeing errors. Try turning on "pause on caught exceptions" in Chrome's devtools to see the error. And I just realized that it's because of your this scoping, but it looks lke you already figured that out. Obviously you shouldn't use that=this, use a fat arrow function in for the .on() handler, like you do for the parent loadScript callback
    – Andy Ray
    Commented Mar 2, 2023 at 6:48

You must log in to answer this question.

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