Fluentui: DetailsList: onColumnClick after the sorting the list is not re-rendered

Created on 17 Dec 2018  ·  3Comments  ·  Source: microsoft/fluentui

Environment Information

  • __Package version(s)__: 6.114.0
  • __Browser and OS versions__: Windows 10 and latest Chrome

Please provide a reproduction of the bug in a codepen:

/** On Column Click: Event Handler */
// tslint:disable-next-line:no-any
private _onColumnClick = (event: React.MouseEvent<HTMLElement>, column: any) => {
    let { sortedItems, columns } = this.state;
    const { dateFormat } = this.props;
    let isSortedDescending = column.isSortedDescending;
    if (column.noSorting) {
        event.preventDefault();
    } else {
        if (sortedItems && columns) {
            // If we've sorted this column, flip it.
            if (column.isSorted) {
                isSortedDescending = !isSortedDescending;
            }                
            // To check if the column data is empty or not
            let columnData = sortedItems.filter(item => item[column.fieldName] !== '' &&
                item[column.fieldName] !== null && item[column.fieldName] !== undefined);
            // To check if the column is having the same data or not
            columnData = columnData.filter((item, pos, array) => {return array.
                map(mapItem => mapItem[column.fieldName]).indexOf(item[column.fieldName]) === pos; });
            // columnData should have more than 1 object in the list to sort and more than 1 unique value to compare
            if (columnData.length === 1) {
                sortedItems = sortedItems.sort((a, b) => {
                    // To check the data is not NULL or UNDEFINED(If so, it should come last)
                    if (a[column.fieldName] === null || a[column.fieldName] === undefined ||
                        a[column.fieldName] === '') {
                        return 1;
                    } else {
                        return -1;
                    }
                });
            } else if (columnData.length > 1 ) {
                // Sort the items.
                sortedItems = sortedItems.sort((a, b) => {
                    let firstValue, secondValue;
                    // To check the data is not NULL or UNDEFINED(If so, it should come last)
                    if (a[column.fieldName] === null || a[column.fieldName] === undefined || 
                        a[column.fieldName] === '') {
                        return 1;
                    } else if (b[column.fieldName] === null || b[column.fieldName] === undefined || 
                        b[column.fieldName] === '') {
                        return -1;
                    } else {
                        // To check the data if it is a boolean
                        if (typeof a[column.fieldName] === 'boolean' && typeof b[column.fieldName] === 'boolean') {
                            firstValue = a[column.fieldName];
                            secondValue = b[column.fieldName];
                        // To check the data is of Number type
                        } else if (!isNaN(a[column.fieldName]) && !isNaN(a[column.fieldName])) {
                            firstValue = a[column.fieldName];
                            secondValue = b[column.fieldName];
                        // To check the data is of Date type
                        } else if (moment(a[column.fieldName], dateFormat, true).isValid() && 
                            moment(b[column.fieldName], dateFormat, true).isValid()) {
                            firstValue = moment(a[column.fieldName]);
                            secondValue = moment(b[column.fieldName]);
                        // The rest is expected to be string
                        } else {
                            firstValue = a[column.fieldName].toLowerCase();
                            secondValue = b[column.fieldName].toLowerCase();
                        }
                    }                         
                    if (isSortedDescending) {
                        return firstValue > secondValue ? -1 : 1;
                    } else {
                        return firstValue > secondValue ? 1 : -1;
                    }
                });                    
            } else {
                event.preventDefault();
            }
            // Reset the items and columns to match the state.
            if (sortedItems.length > 0) {
                this.setState({
                    sortedItems: sortedItems,
                    isSorted: true,
                    columns: columns.map(col => {
                        col.isSorted = (col.key === column.key);

                        if (col.isSorted) {
                            col.isSortedDescending = isSortedDescending;
                        }

                        return col;
                    })
                });
            }
        }
    }
}

/** DetailsList Usage */
<DetailsList
    items={sortedItems}
    columns={columns}
    layoutMode={layoutMode || DetailsListLayoutMode.justified}
    selection={selection}
    onRenderItemColumn={onRenderItemColumn || undefined}
    setKey="set"
    onColumnHeaderClick={this._onColumnClick}
    isHeaderVisible={true}
    checkboxVisibility={CheckboxVisibility.hidden}
    selectionMode={selectionMode || SelectionMode.none}
    onActiveItemChanged={onActiveItemChanged ? onActiveItemChanged : undefined}
/>

Actual behavior:

there is no visible change on the table

Expected behavior:

The table should update immediately with the sorted list.

On investigating, I could find in DetailsList, newProps and this.props both are updated with the new sorted list. So, in DetailsListBase.prototype.componentWillReceiveProps newProps.items !== items // false thus the rendering is not triggered.

Priorities and help requested:

Are you willing to submit a PR to fix? (Yes, No)

Requested priority: (Blocking, High, Normal, Low)

Products/sites affected: (if applicable)

DetailsList Question ❔

Most helpful comment

DetailsList will only re-render if a new array of items has been passed in. Array.prototype.sort sorts arrays in-place, so the array being passed to setState and the DetailsList is actually the same one as before.

To get the list to re-render, you can copy the array before sorting: for example,
sortedItems = [...sortedItems].sort(/*...*/). Let me know if this helps.

All 3 comments

DetailsList will only re-render if a new array of items has been passed in. Array.prototype.sort sorts arrays in-place, so the array being passed to setState and the DetailsList is actually the same one as before.

To get the list to re-render, you can copy the array before sorting: for example,
sortedItems = [...sortedItems].sort(/*...*/). Let me know if this helps.

Thanks @ecraig12345 . Your suggestion worked. Really appreciate your fast response.

Was this page helpful?
0 / 5 - 0 ratings