I have a custom lwc that displays a lightning-datatable and I want some rows to have its checkbox disabled.

Is it possible to set this dynamically? Or do I have to manage this via onrowselection function?

1 Answer 1


This is possible. With web components, we have the ability to extend existing classes if those classes have the export keyword.

Using browser developer tools, the lightning-datatable component loads a JS file called datatable.js. In this file there is class LightningDatatable( the default export) which has a function updateRowsState() and updateColumns() . We need to override these function in a new custom component by removing the this. notation on the function updateRowsAndCellIndexes which is called from updateColumns() and updateRowsState() functions which we are overriding.

So create a new LWC called "customDataTable" which extends the out of the box lightning-datatable.

The goal here to disable a row is rows array in the "state" variable. Each element in rows is of type checkbox and has an attribute "isDisabled". You need to write a logic to set isDisable = true based upon values from the state.data array (this is the data that will get rendered on the table). This is the function "isDataRowDisabled".

Now also copy the function updateRowsAndCellIndexes and modify it as below.

Note: this does solve a specific purpose. But there are risks if Salesforce change the code for the base components. Also you will need to import a lot functions from the base javascript file for this change to work else you will get a compiling error.

Onrowselected is not a good user experience.

Hope this helps.

import LightningDataTable from 'lightning/datatable';

            export default class customDataTable extends LightningDataTable {

                constructor() {

updateRowsAndCellIndexesNew() {
        const {
            privateTypes: types
        } = this;
        const {
        } = state;
        const data = getData(state);
        const columns = getColumns(state);
        const {
        } = createRowKeysGenerator(keyField);
        const scopeCol = columns.find(colData => types.isValidType(colData.type) && colData.isScopeCol); // initializing indexes

        state.indexes = {};
        state.rows = data.reduce((prev, rowData, rowIndex) => {
            const row = {
                key: computeUniqueRowKey(rowData),
                // attaching unique key to the row
                cells: []
            const rowErrors = getRowError(state, row.key);
            state.indexes[row.key] = {
            row.inputType = getRowSelectionInputType(state);
            row.isSelected = isSelectedRow(state, row.key);
            row.ariaSelected = row.isSelected ? 'true' : false;
            //row.isDisabled = isDisabledRow(state, row.key);
            row.isDisabled =  this.isDataRowDisabled(state, row.key);
            row.classnames = resolveRowClassNames(row);
            Object.assign(row, getRowStateForTree(rowData, state));
            row.tabIndex = -1;
            columns.reduce((currentRow, colData, colIndex) => {
                const {
                } = colData;
                const colKeyValue = generateColKeyValue(colData, colIndex);
                const dirtyValue = getDirtyValue(state, row.key, colKeyValue);
                const computedCellValue = dirtyValue !== undefined ? dirtyValue : rowData[fieldName];
                const colType = types.getType(colData.type); // cell object creation

                const cell = {
                    columnType: colData.type,
                    columnSubType: colData.typeAttributes ? colData.typeAttributes.subType : undefined,
                    dataLabel: colData.label,
                    value: computedCellValue,
                    // value based on the fieldName
                    rowKeyValue: row.key,
                    // unique row key value
                    // unique column key value
                    tabIndex: -1,
                    // tabindex
                    isCheckbox: colData.type === 'SELECTABLE_CHECKBOX',
                    class: computeCellClassNames(colData, rowErrors, dirtyValue),
                    hasError: rowErrors.fieldNames && rowErrors.fieldNames.includes(colData.fieldName),
                    isDataType: types.isValidType(colData.type) && !colData.isScopeCol,
                    isDataTypeScope: types.isValidType(colData.type) && colData.isScopeCol,
                    wrapText: state.wrapText[colKeyValue],
                    // wrapText state
                    style: colType && colType.type === 'custom' ? 'padding: 0px;' : undefined

                if (isCustomerColumn(colData)) {
                    Object.assign(cell, computeCellTypeAttributes(rowData, colData, types), computeCellAttributes(rowData, colData), computeCellEditable(colData));

                    if (isTreeType(colData.type)) {
                        Object.assign(cell, computeCellStateTypeAttributes(row));
                } else if (isRowNumberColumn(colData)) {
                    const scopeColValue = rowData[scopeCol.fieldName];
                    const errorColumnDef = getRowNumberErrorColumnDef(rowErrors, scopeColValue);
                    Object.assign(cell, computeCellTypeAttributes(rowData, errorColumnDef, types));
                } // adding cell indexes to state.indexes
                // Keeping the hash for backward compatibility, but we need to have 2 indexes, 1 for columns and one for rows,
                // because of memory usage and also at certain point we might have the data but not the columns

                state.indexes[row.key][colKeyValue] = [rowIndex, colIndex];
                return currentRow;
            }, row.cells);
            return prev;
        }, []);

                updateColumns(columns) {
                        const {
                        } = this;
                        const hadTreeDataTypePreviously = hasTreeDataType(state); // calculate cell to focus next before indexes are updated

                        normalizeColumns(state, columns, this.privateTypes);
                        setDirtyValues(state, this._draftValues);
                        updateRowNavigationMode(hadTreeDataTypePreviously, state);
                        state.headerIndexes = generateHeaderIndexes(getColumns(state));
                        adjustRowNumberColumnWidth(this.template, state);
                        updateColumnWidthsMetadata(state); // set the celltofocus next to null if the column still exists after indexes calculation


                        if (getColumns(state).length !== getColumnsWidths(state).length) {

                          if (getData(state).length > 0) {
                            // when there are column changes, update the active cell
                        } else if (hasDefinedColumnsWidths(state)) {
                          // try to adjust column size if previous size in the state and table is visible (not hidden)
                          if (isTableRenderedVisible(this.template)) {
                            adjustColumnsSize(this.template, state);
                          } else {

                        if (state.keyboardMode === 'NAVIGATION') {

                        if (state.cellToFocusNext && state.activeCell) {
                          setFocusActiveCell(this.template, this.state);

isDataRowDisabled(state, rowKey) {
        if(state) {
            let isDisabled = 'isDisabled'; //This is a field in your data from apex (i always wrapper sobjects into a class)
            let keyField = state.keyField;
            let data = state.data;
            let matchedDataRow = data.find( y => y[keyField] === rowKey);
            return (matchedDataRow && matchedDataRow[isDisabled]);
        return false;

You must log in to answer this question.

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