lwc icon indicating copy to clipboard operation
lwc copied to clipboard

Datatable Column action check mark not updating when using Header-Level Actions and @track

Open magnadrew opened this issue 5 years ago • 10 comments

Apologies if this is not correct repo but not sure where to post issues about BaseComponents...

I followed documentation for "Creating Header-Level Actions" (https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation)

The actions fire ok but the column header does not reflect the new state. Instead it always stays as "All". If you change to XXX it will show All. I did notice that if you toggle between cilp / wrap then the header updates.

You can see it in action at (https://developer.salesforce.com/docs/component-library/tools/playground/huueFbBVc/edit)

The template is

<template>
    <lightning-card title="Test LDT Header action">
        <lightning-datatable data={data} 
                    hide-checkbox-column=true   
                    columns={columns} 
                    key-field="Id"  
                    onheaderaction={handleHeaderAction}>
            </lightning-datatable>      
        </lightning-card>
</template>

JavaScript:

export default class LdtHeaderAction extends LightningElement {

    rawData = [
        { Col1: '1', Col2: 'XXX' },
        { Col1: '2', Col2: 'XXX' },
        { Col1: '3', Col2: 'YYY' },
        { Col1: '4', Col2: 'YYY' },
        { Col1: '5', Col2: 'ZZZ' },
    ];

    @track columns = [
        {
            label: 'Col 1',
            fieldName: 'Col1',
            initialWidth: 100
        },
        {
            label: 'Col 2',
            fieldName: 'Col2',
            actions: [
                { label: 'All', checked: true, name:'all' },
                { label: 'XXX', checked: false, name:'filter_xxx' },
                { label: 'YYY', checked: false, name:'filter_yyy' },
                { label: 'ZZZ', checked: false, name:'filter_zzz' },
            ]
        },
    ];


    @track data = this.rawData;
    @track filter ='all';

    handleHeaderAction(event) {
        const actionName = event.detail.action.name;
        const cols = this.columns;
        const filter = this.filter;

        if (actionName !== filter) {
            this.filter = actionName;
            this.filterCol2();

            //set col action as checked 
            let actions = cols[1].actions;
            actions.forEach((action) => {
                action.checked = action.name === actionName;
            });
            //force header check change
            this.columns = cols;
            // eslint-disable-next-line no-console
            console.log(JSON.stringify(this.columns, null, '\t'));
        }
    }

    filterCol2 () {
        const rows = this.rawData;
        const filter = this.filter;
        let filteredRows = rows;
        if (filter !== 'all') {
            filteredRows = rows.filter(function (row) {
                let test = 'filter_'+row.Col2.toLowerCase() === filter;
                return test;
            });
        }
        this.data = filteredRows;
    }

}

magnadrew avatar Nov 13 '19 18:11 magnadrew

Yes, this is the right repo for this kind of issue, because the problem here is an intersection between components and engine. In this case, you're facing an issue related to #1444 (reactive setters), which is not fixed but behind a flag due to the potential implications of enabling this today.

Specifically, the problem is not your code, your code is correctly tracking the data structure for columns, and passing it down to the datatable, and eventually mutating the tracked structure, but the datatable is unable to detect such mutation because it is using a setter that is not reactive to normalize the columns when passed down from parent.

As a workaround, you will have to provide a brand new object for columns whenever you make a change onto them. Specifically, this line: this.columns = cols; does nothing because you're assigning the same value, basically, you're doing this.columns = this.columns, which is a no-op. Just create a new array for the columns, that should help.

Again, in the future, your code will work just fine, but for now, you have to force it to not be === otherwise datatable will not pick that up.

/cc @garazi @gonzalocordero @reiniergs

caridy avatar Nov 19 '19 18:11 caridy

As a workaround, this.columns = JSON.parse(JSON.stringify(cols); It should resolve this issue.

brahmajitammana avatar Apr 04 '20 18:04 brahmajitammana

Yes, because that creates a fresh data structure that the reactivity system can pick up, and render accordingly. cc @Gr8Gatsby

caridy avatar Apr 05 '20 00:04 caridy

@caridy Thank you for your responses. Any idea when #1444 would be resolved?

sagarpomal avatar Apr 11 '20 19:04 sagarpomal

@sagarpomal that's probably a question for @Gr8Gatsby, it is behind a flag for now.

caridy avatar Apr 12 '20 18:04 caridy

I can not get the workarounds to work for this. Tested both this.columns = JSON.parse(JSON.stringify(cols); and this.columns = [...cols]; without any luck. It is incredible annoying as the users are very annoyed and request it to be fixed, and we can not do anything about it.

My current implementation:

handleProductHeaderAction(event) {
        const actionName = event.detail.action.name;
        const colDef = event.detail.columnDefinition;
        const columns = this.productColumns;
        const activeFilter = this.activeFilter;

        if (actionName !== activeFilter && actionName !== 'wrapText' && actionName !== 'clipText') {
            if (actionName !== undefined && actionName !== 'all') {
                this.products = this.allProducts.filter(_product => _product[colDef.label] === actionName);
            } else if (actionName === 'all') {
                this.products = this.allProducts;
            }

            this.activeFilter = actionName;
            columns.find(column => column.label === colDef.label).actions.forEach(action => action.checked = action.name === actionName);
            this.columns = [...columns];
            console.log("this.columns: " + JSON.stringify(this.columns, null, '\t'));
        }
}

Hope for a solution to this very quickly @caridy and @Gr8Gatsby. ;-)

Triopticon avatar May 08 '20 23:05 Triopticon

@Triopticon try: this.columns = JSON.parse(JSON.stringify(this.columns);

sagarpomal avatar May 09 '20 07:05 sagarpomal

@Triopticon try: this.columns = JSON.parse(JSON.stringify(this.columns);

Hi @sagarpomal, Thank you for your kind reply. I have tested that method also without any luck.

Doing the this.columns = JSON.parse(JSON.stringify(this.columns); has the same effect as using the es6 spread operator normally this.columns = [...columns];.

But none of these two methods is working for me in this case, unfortunately.

Triopticon avatar May 09 '20 14:05 Triopticon

@Triopticon try: this.columns = JSON.parse(JSON.stringify(this.columns);

Hi @sagarpomal, Thank you for your kind reply. I have tested that method also without any luck.

Doing the this.columns = JSON.parse(JSON.stringify(this.columns); has the same effect as using the es6 spread operator normally this.columns = [...columns];.

But none of these two methods is working for me in this case, unfortunately.

@Triopticon happy to get on a call

sagarpomal avatar May 13 '20 06:05 sagarpomal

@caridy @Gr8Gatsby

#1444 has been released without a flag, presumably, but this problem still persists.

I was surprised to find out this was a problem, and I even created a demo for this.

Is this the most appropriate issue to log this against, or would a more recent issue be better?

brianmfear avatar Jan 09 '22 15:01 brianmfear