0

I have pushed some values into an array inside the @wire method and then when I try to console log those values through the renderedCallBack() function I get undefined, also if I try to access those values to create map markers I get "afterRender threw an error" error inside the same renderedCallBack() function. Would this be something related to the order of execution?

Two ways I tried console logging inside the renderedCallBack() function:

1. console.log(this.provinces);// I get the values inside proxy.
2. console.log(this.provinces[0]);//I get undefined when I access the first row of the array

Here is the minified code:

 @track provinces = [];
 @wire(onLoadProvinces) provStats({data}) {
    if (data) {
        this.provinces.push({
          "key": data[i].Id,
          "province": data[i].Name,
          "latitude": latitude,
          "longitude": longitude
        });
      }
    }
  }

  renderedCallback() {
    console.log(this.provinces);//this shows values inside proxy
    Promise.all([
      loadStyle(this, leaflet + '/leaflet.css'),
      loadScript(this, leaflet + '/leaflet.js')
    ]).then(() => {
      const el = this.template.querySelector(".map-root");
      let mymap = L.map(el).setView([54.139503, -96.653471], 4);
      L.tileLayer('https://api.mapbox.com/styles/, {
        tileSize: 512,
        zoomOffset: -1,
      }).addTo(mymap);
       let bcPopup = L.circle([this.provinces[0].latitude, this.provinces[0].longitude], {//this throws the afterRender error.
         color: 'red',
         fillColor: '#f03',
         fillOpacity: 0.5,
         radius: 500
       }).addTo(mymap);
    });
  }

EDIT [Based on Ronnies answer] Thanks @Ronnie,

I put everything inside the async connectedCallBack method, which means everything that I had under the @wire method and the renderedCallBack method previously. Still when I console log(this.provinces[0]) I get undefined, but when I do console.log(this.provinces) I see the all the values in proxy.

Below is the edited code: I have edited this with my latest try, here when I run it I get an error saying provinces.map is not a function. And also I learned when we are using map we don't need to use a for loop as the map runs on every single record of the array, also here I am populating the values of latitude and longitude based on the value of a single field returned from apex call, ho would I go about doing that?

     async loadProvinces() {
    return await onLoadProvinces()
      .catch(error => console.log("Error loading provinces: ", error));
  }
      async connectedCallback() {
        const el = this.template.querySelector(".map-root");
        const provinces = this.loadProvinces();

    if (provinces) {
      let latitude = 54.630088;
      let longitude = -125.118077;
      this.provinces = [...provinces.map(data => {
        return {
          "key": data[i].Id,
          "province": data[i].Name,
          "latitude": latitude,
          "longitude": longitude
        }
      })]
    }
    await Promise.allSettled([
      loadStyle(this, leaflet + '/leaflet.css'),
      loadScript(this, leaflet + '/leaflet.js')
    ]).catch(error => console.error(error));

    if (L) {
      let mymap = L.map(el).setView([54.139503, -96.653471], 4);
      L.tileLayer('https://api.mapbox.com/styles/v1/', {
        tileSize: 512,
        zoomOffset: -1,
      }).addTo(mymap);
    }
  }
  • When you're using async, you should use the await keyword instead of the .then().catch() check the syntax of my example below. The same for promises can be done with imperative calls. – Ronnie Apr 5 at 18:30
1

simple solution is to do this in the connectedCallback method since the renderedCallback may be called before you have actually done anything. you can use asynchronous programming here as well by declaring your method async

async connectedCallback(){
  // call your promises and wait for them to complete (resolved|rejected)
  await Promise.allSettled([
      loadStyle(this, leaflet + '/leaflet.css'),
      loadScript(this, leaflet + '/leaflet.js')
    ])
    .catch( error => console.error( error ));

... your other logic

}

this will allow your code to pause while awaiting for these promises to resolve, and then you can do what you need.

I would suggest before using the variables loaded by your scripts that you check for existence first to prevent behavior that you do not want. you can even use this chance to set any attributes to stop rendering for user experience and set an error display on your view should this fail.

if( L ) {
  ... do my thing here
}

EDIT

after your edit, here is a little change to how i would write this. disclaimer: without the relevant code in the apex i cannot reliably test this and log it, but this JS should suffice

JS

async connectedCallback() {
    const el = this.template.querySelector(".map-root");
    const provinces = this.getProvinces();

    // check that the provinces were returned if the catch was reached, this will be undefined
    if( provinces ){

        // use the array spread to assign a new array while spreading the results here.
        // array spread (...) solves the depth issues that track has with objects and arrays
        // that will cause the track variable to see no difference. so we assign it a completely new array
        // with all of the values we need. you can do this anytime that you want to update the array.
        // you can also now update an object by updating the key and it being marked @track see docs for reference
        this.provinces = [ ...provinces.map( record => {
                return {
                    key : record.Id,
                    province : record.Name,
                    latitude : latitude,
                    longitude : longitude
                };
            })
        ]
    }

    // load the scripts asynchronously and wait for the promises to resolve/reject
    await Promise.allSettled([
      loadStyle(this, leaflet + '/leaflet.css'),
      loadScript(this, leaflet + '/leaflet.js')
    ]).catch(error => console.error(error));

    // check that the scripts were loaded correctly as we should now have an "L" available to use. 
    if( L ){

        let mymap = L.map(el).setView([54.139503, -96.653471], 4);

        L.tileLayer('https://api.mapbox.com/styles/v1/sumchans/', {
            tileSize: 512,
            zoomOffset: -1,
        }).addTo(mymap);
    }
}

// reusable call that you can use later will be undefined returned if the catch was called
// else you will have your result from your imperative call.
async getProvinces(){
    return await onLoadProvinces()
                    .catch( error => console.error( "error loading provinces: ", error ) );;
}

Note that i put my const variables at the top of the method, this is my own personal preference since these are not reliant on other methods. better to keep one time set variables in one place when able. This is just a preference for my own organization.

To learn more about the usage of the spread operator for arrays and objects, please see HERE. the spread operator can be used on objects and arrays alike and is widely supported and works well to get around the issues with @track as it relates to array and object depth. More information about this is HERE for supported Javascript and HERE for more information about the decorators

Update 2

If you are seeing your response as an Object once you get it back from the imperative call, you will need to make it an array before using map. Or you can check if it is only 1 record. I don't know what Type you are returning, i can only asume it is a List as you plan to place it in an array. if for whatever reason you are and it returns as an Object you can verify this by adding a check if it is an array.You can always ensure you have an array by creating some validation ahead of usage.

Original

const provinces = this.getProvinces();

Simple Method

// convert any Object to an array, if you're expecting an array (typical when Salesforce returns an object with all number keys )
getObjectAsArray( obj ) {
   return obj && !Array.isArray(obj) 
         ? Object.keys(obj).map(key => obj[ key ]) 
         : obj || []
}

Updated fetch

const provinces = this.getObjectAsArray( this.getProvinces() );
| improve this answer | |
  • Thanks @Ronnie, Still I was not able to make it work, I have put an edit to my original post, could you please check it out. – sumchans Apr 5 at 17:21
  • 1
    You should use an array spread instead of push. So this.provinces = [ ...this.provinces, <my new object here> ] . Track will not see the change due to depth issues, so reassigning it this way will assure that it will see your change. You may see that your data, although you returned a list, may be an object when it returned via imperative call. – Ronnie Apr 5 at 18:23
  • Also consider using data.map to iterate and fill your results array. this.provinces = [ ...data.map(province=>{ ... Logic return province; }) ] – Ronnie Apr 5 at 18:25
  • Would you be able to provide a good code sample with relation to my code. – sumchans Apr 5 at 18:48
  • 1
    Thanks @Ronnie, need to learn quite a few things here on javascript, that way I get to know what I am exactly doing. – sumchans Apr 6 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.