[SalesForce] LWC – Why is the computed rowId for the custom data type in lighting-datatable undefined

I have a custom lighting-datatable where the last row is a column that contains two buttons. When either of these buttons is pressed, I am attempting to retrieve the rowId of the row where the button was pressed.

In my attempt to retrieve the rowId I followed the code example in the section 'Creating Custom Datatypes' from this page: https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation

The rowId is being returned as 'undefined' in my datatable implementation.

I have removed as much unnecessary code as I could from my implementation below. If you believe I've left something important out, please let me know.

The generalized construction of this custom datatable is like so:

myCustomDatatableImplementation.html

<template>
    <lightning-card title="Account Details" icon-name="utility:money">
        <c-my-custom-datatable-implementation
            key-field="id"
            data={data}
            columns={columns}
            onfirstbuttonpressed={handleFirstButtonPressed}
            onsecondbuttonpressed={handleSecondButtonPressed}
            hide-checkbox-column   
        >
        </c-my-custom-datatable-implementation>
        </lightning-card>
</template>

myCustomDatatableImplementation.js

import { LightningElement, track, api } from 'lwc';

export default class myCustomDatableImplementation extends LightningElement {

    @track columns = columns;
    @track data;

    // Get the accounts
    async connectedCallback() {
       // retrieve and set data
    }

    // This handles the event dispatched from  
    // myTwoButtons.js when the first button is pressed
    handleFirstButtonPressed(event) {

        const { rowId } = event.detail;
        // rowId is undefined here

    }

     // This handles the event dispatched from  
    // myTwoButtons.js when the second button is pressed
    handleSecondButtonPressed(event) {
        const { rowId } = event.detail;
        // rowId is undefined here
    }


}

customDatatable.html

<template>
    <custom-datatable
        key-field="id"
        data={data}
        columns={columns}
        onclick={onclick}
        hide-checkbox-column
    >
    </custom-datatable>
</template>

customDatatable.js

import LightningDatatable from 'lightning/datatable';
import buttons from './buttonTemplate.html';

export default class customDatatble extends LightningDatatable {

    static customTypes = {
        buttonsToShow: {
            template: buttons,
            typeAttributes: ['attr']
        }
    };

}

buttonTemplate.html

<template>
    <c-my-two-buttons row-id={value}>
    </c-my-two-buttons>
</template>

myTwoButtons.html

    <template> 

            <div>
                    <lightning-button variant="neutral" title="Toggle content action" label="First Button"
                    onclick={handleFirstButtonPressed}>
                    </lightning-button>
            </div>

            <div>
                    <lightning-button variant="neutral" title="Toggle content action" label="Second Button"
                    onclick={handleSecondButtonPressed}>
                    </lightning-button>
            </div>


</template>

myTwoButtons.js

import { LightningElement, track, api } from 'lwc';

export default class myTwoButtons extends LightningElement {

    @api rowId;


    handleFirstButtonPressed() {   
        const selectEvent = new CustomEvent('firstbuttonpressed', {
            composed: true, 
            bubbles: true, 
            detail: {
                rowId: this.rowId,
            },
        });
        this.dispatchEvent(selectEvent);
    }

    handlePayeePressed() {
        const selectEvent = new CustomEvent('secondbuttonpressed', {
            composed: true, 
            bubbles: true, 
            detail: {
                rowId: this.rowId,
            },
        });
        this.dispatchEvent(selectEvent);
    }

}

Best Answer

I ended up resolving my issue and in doing so I believe that the documentation ( https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation) for 'Creating Custom Data Types' has a few errors. I have provided an explanation of one of these errors below.

I think my initial error lay in the belief that the 'value' property that's assigned to the attribute 'row-id' in buttonTemplate.html was a global property that we don't have to compute ourselves (this is the impression the documentation gave me as no value is set for in in the JavaScript). This may be the case and I may be using it wrong, but I couldn't figure out a solution. Instead, I used typeAttributes like so to assign a rowId myself.

Here is how I solved my rowId issue.

Assign row-id a value using typeAttributes:

buttonTemplate.html

<template>
    <c-my-two-buttons row-id={typeAttributes.rownumber}>
    </c-my-two-buttons>
</template>

Define this typeAttribute in the custom datatable:

buttonTemplate.js

import LightningDatatable from 'lightning/datatable';
 import buttons from './buttonTemplate.html';
 export default class customDatatble extends LightningDatatable {

            static customTypes = {
                buttonsToShow: {
                    template: buttons,
                    typeAttributes: ['rownumber']
                }
            };

        }

Finally, wherever you use the custom datatable, set the type attribute when you're setting the data:

myCustomDatatableImplementation.js

    import { LightningElement, track, api } from 'lwc';

const accountColumns = [
    { label: 'First Col', fieldName: 'firstCol', type: 'text'},
    { label: 'Second Col', fieldName: 'secondCol', type: 'text'},
    { label: 'Buttons', fieldName: 'buttons', type: 'buttons', typeAttributes: {
            rownumber: {fieldName: 'rowIndex'}
        }
    }
];

    export default class myCustomDatableImplementation extends LightningElement {

        @track columns = columns;
        @track data;

        // Get the accounts
        async connectedCallback() {
           // retrieve and set data
           // When data is retrived, iterate through it and set rownumber
           var i = 0;
           for(i; i < data.length; i++) { 
               data[i].rowIndex = i;
           }
        }

        // This handles the event dispatched from  
        // myTwoButtons.js when the first button is pressed
        handleFirstButtonPressed(event) {

            const { rowId } = event.detail;
            // rowId is undefined here

        }

         // This handles the event dispatched from  
        // myTwoButtons.js when the second button is pressed
        handleSecondButtonPressed(event) {
            const { rowId } = event.detail;
            // rowId is undefined here
        }


    }

You will now be able to dispatch the rowId up the hierarchy from the button pressed.

Now, here is why I believe the documentation may be erroneous. If I have misunderstood something, please let me know!

For example, the documentation states:

The wrapper component displays your custom datatable component.
<template>
    <demo-my-datatable
        key-field="id"
        data={data}
        columns={columns}
        ondeleterow={deleteRow}
        hide-checkbox-column>
    </demo-my-datatable>
</template>

This wrapper uses the following JavaScript class:

import { LightningElement, track } from 'lwc';
/* Import module that fetches data */
import fetchRecords from './fetchData';

const columns = [
    // Your column data here
    {
        label: '',
        type: 'deleteRowButton',
        fieldName: 'id',
        fixedWidth: 70,
        typeAttributes: {
          attrA: { fieldName: 'attrA' },
          attrB: { fieldName: 'attrB' },
        },
    },
];

export default class DatatableAppExExample extends LightningElement {
    @track data = [];
    @track columns = columns;

    connectedCallback() {
        fetchRecords(10).then(
          // Display your data
          // Set your attribute values (attrA, attrB, ...) here
        );
    }

    deleteRow(event) {
        const { rowId } = event.detail;
        // Remove the row
    }
}

It was my understanding that the markup must use the kebab case equivalent of it's JavaScript controller with 'c' prepended. Therefore the mark-up should be:

 <template>
        <c-datatable-app-ex-example
            key-field="id"
            data={data}
            columns={columns}
            ondeleterow={deleteRow}
            hide-checkbox-column>
        </c-datatable-app-ex-example>
    </template>

This is only one of the potential issues I've noticed.