20

I have created an LWC component for quick action and also defined the @api recordId inside that. But the recordId is undefined in the component when logged. Seems like it is not being passed to the component from quick action. Why is it undefined?

12 Answers 12

26

Implement a setter for the Id and add your logic here to be executed as soon as the value is populated by SF internally.

_recordId;

@api set recordId(value) {
    this._recordId = value;

    // do your thing right here with this.recordId / value
}

get recordId() {
    return this._recordId;
}
2
  • 1
    This works and I believe should be marked as the correct answer. Putting an arbitrary delay or "display:none" div are both work-arounds that add overhead. This is IMHO the correct answer.
    – Paul N
    Commented May 24, 2022 at 12:21
  • 1
    I couldn't get this to work with a headless action. It seems there is an assumption the recordId is going to be set at some stage, but this doesn't seem to be the case?
    – bpilling
    Commented Apr 13, 2023 at 4:49
8

I had the same issue, was able to fix the issue by implementing this URL parameter retrieval piece of code.

import
import { LightningElement, wire  } from 'lwc';
import {CurrentPageReference} from 'lightning/navigation';
Get URL Value and assign to the recorded variable.
@wire(CurrentPageReference)
getStateParameters(currentPageReference) {
    if (currentPageReference) {
        this.recordId = currentPageReference.state.recordId;
    }
}
1
  • This perfectly work and kind of best OOTB Solution without doing any tricks or workaround. Also in Below comments by @Rahul Gawale its explained why we are not getting recordId in Quick Action. Commented Jun 4, 2022 at 11:59
6

You can check these QuickAction recipes from the lwc-recipes app of Trailhead Sample Gallery. These recipes are newly added and showcase different use-cases of Quick actions with lwc.

editRecordScreenAction - his simple recipe shows how to create a screen action with LWC. It uses recordId, retrieves the data using wire service, and updates the record using uiRecordApi.

Here is a sample usage of recordId from this recipe.

import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import FIRSTNAME_FIELD from '@salesforce/schema/Contact.FirstName';
import LASTNAME_FIELD from '@salesforce/schema/Contact.LastName';

export default class EditRecordScreenAction extends LightningElement {
    @api recordId;
    @api objectApiName;

    @wire(getRecord, {
        recordId: '$recordId',
        fields: [FIRSTNAME_FIELD, LASTNAME_FIELD]
    })
    contact;

    get firstname() {
        return this.contact.data
            ? this.contact.data.fields.FirstName.value
            : null;
    }

    get lastname() {
        return this.contact.data
            ? this.contact.data.fields.LastName.value
            : null;
    }
}

You need to set this target configuration in the metafile.

 <targets>
    <target>lightning__RecordAction</target>
 </targets>
 <targetConfigs>
    <targetConfig targets="lightning__RecordAction">
        <actionType>ScreenAction</actionType>
    </targetConfig>
 </targetConfigs>

dispatchEventHeadlessAction - This recipe shows how you can create Headless Actions. It depicts how you can dispatch an event with the click of a button.

Here is the sample javascript code for the headless action from this recipe.

import { LightningElement, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class DispatchEventHeadlessAction extends LightningElement {
    @api recordId;
    @api async invoke() {
        // Fire Toast message
        let event = new ShowToastEvent({
            title: 'I am a headless action!',
            message: 'Hi there! Starting...'
        });
        this.dispatchEvent(event);
        // Wait and fire another another Toast message
        await this.sleep(2000);
        // Fire Toast message
        event = new ShowToastEvent({
            title: 'I am a headless action on record with id ' + this.recordId,
            message: 'All done!'
        });
        this.dispatchEvent(event);
    }

    sleep(ms) {
        // eslint-disable-next-line @lwc/lwc/no-async-operation
        return new Promise((resolve) => setTimeout(resolve, ms));
    }
}

You need to set this target configuration for headless action in the metafile.

<targets>
  <target>lightning__RecordAction</target>
</targets>

<targetConfigs>
  <targetConfig targets="lightning__RecordAction">
    <actionType>Action</actionType>
  </targetConfig>
</targetConfigs>
0
3

EDIT: This definitely feels like a bit of a hack and a workaround, but it works until Salesforce gets us access to recordId in the connectedCallback() for Screen Action LWCs

Piggy backing off of Manikanta's answer here but this solution finally worked for me for LWC Screen Quick Actions where you need access to the record Id and it isn't available in the connectedCallback() function.

In the Javascript file we can wait until we hit rendered callback with access to the record Id by setting up that display none div with the recordId being rendered. You'll notice that the renderedCallback() function ends up getting called twice in our component. The first time on initial render, and a second time when the {recordId} variable is rendered on the page. The second render is what gives us access to the recordId.

Once we have access to the recordId we need to set retrievedRecordId to true so that we don't continuously execute some body of logic over and over.

Javascript File

import { LightningElement, api } from 'lwc';

export default class QuickActionRecordIdRetrieval extends LightningElement {

    @api recordId;
    retrievedRecordId = false;

    renderedCallback() {
        if (!this.retrievedRecordId && this.recordId) {
            
            this.retrievedRecordId = true; // Escape case from recursion
            console.log('Found recordId: ' + this.recordId);

            // Execute some function or backend controller call that needs the recordId
        }
    }
}

HTML File

<template>
    <div style="display: none;">
        {recordId}
    </div>
</template>
2

Update: If you are using the Headless Action, the recordId is not available until the @api invoke() is called. You can get the recordId in invoke method.

You can get the record in the lwc quick action. Just define the recordId as a public property in your component. Please note the this is case sensitive.

To get the records fields you can use the getRecord wired method from uiRecordApi. Just pass the record Id to the getRecord method.

See the sample code below.

import { LightningElement, api } from "lwc";

export default class LwcScreenAction extends LightningElement {
    @api recordId;

    connectedCallback() {
        console.log("recordId", this.recordId);
    }
}
1
  • 8
    Thanks for responding @Rahul. I tried the below in my lightning component. ` connectedCallback(){ console.log("Inside connectedCallback() ", this.recordId); } }` It gets called, but the recordId shows undefined. recordId is declared with @api in the class. Also, the user s not directed to the lwc template, which is strange too. Any idea what could be going on here
    – Ad A
    Commented May 25, 2021 at 17:28
2

Okay, I think I figured out what was going on. The lightning web components targeted at lightning_RecordAction are headless, so the recordId is not passed to connectedCallback(). However, it is passed to the invoke() method if the recordId has been declared appropriately.

4
  • +1 Please mark your answer as accepted so it will help others. Commented May 26, 2021 at 7:05
  • Did you try adding it as a <target>lightning__RecordPage</target> also?
    – Adrian Larson
    Commented Jun 1, 2021 at 17:32
  • Any chance you can give an example? I do not understand... "However, it is passed to the invoke() method if the recordId has been declared appropriately." Commented Jun 10, 2021 at 15:45
  • @AdrianLarson No, that does not make a difference. Commented Jul 28, 2021 at 11:09
2

Please Try Below code you will get record id:

   @api recordId; 
   connectedCallback() {
        window.clearTimeout(this.delayTimeout);
        this.delayTimeout = setTimeout(() => {
           alert(this.recordId));
        }, 0);
    }
2
  • This works for me too.
    – Keith C
    Commented Jul 19, 2021 at 16:51
  • But for me it doesn't. this.recordId is still undefined. Commented Jul 28, 2021 at 11:34
2

For this to work, you can add {recordId} in the component html and have that in a div with style display:none and you can get the value in renderedCallback. The accepted answer above may not work for Screen type Action

Or

Create a wrapper Aura component which gets the recordId and pass in the recordId to LWC

2

This can be confusing as normally the connectedCallback() guarantees that the recordId is populated. But it is not the case for the lwc quick actions. Explanation from similar question: https://salesforce.stackexchange.com/a/349118/99463

recordId is guaranteed in the connectedCallback() only when you are in specific record context. Even though lightning quick actions are to be used on the record detail pages, the context of the component is not considered as explicit record context and thats why the recordId is not guaranteed in the connectedCallback() function.

Probably best solution how to obtain recordId when the component is opened is to use renderedCallback() and follow the notes in the question's last bullet point.

0

For a LWC Screen Action, use getRecord from the Wire Service wrapped in a function. That way you can use the reactive $recordId and then call init. No need to use connectedCallback() or renderedCallback().

//Call this before init to get recordId
@wire(getRecord, {
    recordId: '$recordId',
    //Add whatever fields that you may need or just use Id
    fields: FIELDS
})
wiredRecord({error, data}) {
    if(data) {
        this.invoice = data;
        //Make sure init only gets called once
        if(!this.executed) {
            this.executed = true;
            this.spinner = true;
            this.init();
        }
    }
    else if(error) {
        this.showToast(error, 'error', 'dismissable');
    }
}
0

By using a wrapper and nesting your main component, the nested component will only render when recordId has a value.

<template>
    <template if:true={recordId}>
        <c-nested-component record-id={recordId}></c-nested-component>
    </template>
</template>
-1

Another alternative is to get the recordid from the URL parameter using windows.location. Here is an example code to get the record ID.

renderedCallback() {
        const urlSearchParams = new URLSearchParams(window.location.search);
        this.recordId = urlSearchParams.get('recordId');
        if (this.recordId) {
            console.log('Found recordId: ' + this.recordId);
            // do something
        }
    }

You must log in to answer this question.

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