Saturday 22 January 2022

refreshApex and Lightning Web Components

Introduction

One of the things I particularly like about Lightning Web Components is the wire service. The ability to let the platform send me the data when it decides things are ready, rather than having to figure out when to call an Apex method based on rendered callbacks and the like really cleaned up my code from the days of Aura.

Updated Records on the Server

The one area that it doesn't automatically handle for me is sending me new data when a record is updated server side, by other Apex code or maybe the controller of the LWC that is using the wire service. Initially my approach was to return the updated records to the component if it was my own controller method carrying out the update, or switch to imperative Apex calls once I knew an update had taken place. Neither of these were particularly satisfactory though.

The solution is the refreshApex method, which you import from salesforce/apex. Per the docs 

Sometimes, you know that the cache is stale. If the cache is stale, the component needs fresh data. To query the server for updated data and refresh the cache, import and call the refreshApex() function.

Exactly what I need, and it keeps my data in the scope of the wire service, so if that gets notified that there is a newer version of the data, I'll get that too.

There's a slight gotcha that makes it easy to get this wrong. When I first tried it I was convinced that it didn't work, because I'd misunderstood a key aspect of the docs. The instruction in question is:

NOTE The parameter you refresh with refreshApex() must be an object that was previously emitted by an Apex @wire.

So in my wire method handler, I captured the object that I'd retrieved:
@wire(GetPage, {name: 'home'})
gotPage(result) {
    if (result.data) {
        this.page=result.data;
        this.dataToRefresh=result.data;
    }

and when I knew the record had been updated server side, I executed the refreshApex method:

getLatest() {
refreshApex(this.dataToRefresh);
}
and nothing happened. I got no errors, but the data didn't change either. As refreshApex returns a promise, I figured maybe the issue was it wasn't resolving, so I added the code to handle success and failure:
getLatest() {
refreshApex(this.dataToRefresh)
    .then(()=> {
        this.dispatchEvent(
            new ShowToastEvent({
                title: 'Success',
                message: 'Refreshed Data',
                variant: 'success'
            })
        );
    })
    .catch((error) => {
        this.dispatchEvent(
            new ShowToastEvent({
                title: 'Error Refreshing Data',
                message: message,
                variant: 'error'
            })
        );
    });
}

this time I got an error, that didn't make a lot of sense to me, but seemed like the refreshApex method wasn't returning a promise, even though it was supposed to return an promise.

As it was the first time I'd used it, I had no idea what the correct usage looked like, so there was a bit of trial and error before I realised that "an object that was previously emitted by an Apex @wire" meant the entire object rather than the data property that I was extracting. And reading further on the docs confirmed this, which reminded me to always RTFM!

Updating my wire method handler to capture the whole thing rather than the records from the server:

@wire(GetPage, {name: 'home'})
gotPage(result) {
    if (result.data) {
        this.page=result.data;
        this.dataToRefresh=result;
    }
and things were all good:


Notice that I don't have to care what data was returned by the wired method, I just save it somewhere and pass it as the parameter.

Example Component

My LWC Blogs repo has an example component that shows refreshApex being used correctly. If you are going to try it out in a scratch org, check the instructions in the README as there's a little bit of setup to get the page to work correctly. 

The component retrieves a Page record that has the name 'Home' and shows how many views it has had. There's a button you can click that increments the views count by calling an Apex method. Once the method completes, the record cached server side is updated using the refreshApex method, and the user receives notifications about all sorts of things:



Related Posts


1 comment:

  1. Hello,

    Thank you for sharing this.

    I've read the docs and multiple articles and blogs and I'm doing exactly the same i.e. I'm passing the entire object returned by wire to refreshApex method but it's still not working.

    My table has infinite scrolling enabled and it doesn't directly show the data returned from wire instead I need to process the data save it in a different array and then display it on the table.

    Could you please suggest if I need to do anything extra for this to work for me as well?

    Thanks!

    ReplyDelete