reactjsoffice-ui-fabricfluent-uifluentui-reactoffice-ui-fabric-react

Rendering custom item columns with sorting


Can anyone help me resolve the typing issues below for a FluentUI details list?

I have the following code to display a list of items using Fluent UI details list. I need to add the functionality to sort items in each column in ascending order like this:

const buildColumns = (): IColumn[] => {
  const columns: IColumn[] = [];
  columns.push({
    key: "name",
    name: "Name",
    fieldName: "name",
    minWidth: 75,
    maxWidth: 75,
    isResizable: false,
    onColumnClick: _onColumnClick
  });
  columns.push({
    key: "age",
    name: "Age",
    fieldName: "age",
    minWidth: 200,
    maxWidth: 200,
    isResizable: false,
    onColumnClick: _onColumnClick
  });
  return columns;
}

function _copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] {
    const key = columnKey as keyof T;
    return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1));
}

const _onColumnClick = (ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
  const [columns, setColumns] = useState<[]>();
  const [names, setNames] = useState<[]>();
  const newColumns: IColumn[] = columns.slice(); // error: columns' is possibly 'undefined'.
  const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0];
  newColumns.forEach((newCol: IColumn) => {
    if (newCol === currColumn) {
    currColumn.isSorted = true;
    currColumn.isSortedDescending = !currColumn.isSortedDescending;
    } else {
    newCol.isSorted = false;
    newCol.isSortedDescending = true;
    }
  });
  const newItems = _copyAndSort(names, currColumn.fieldName!, currColumn.isSortedDescending); // error: Argument of type '[] | undefined' is not assignable to parameter of type 'never[]'.
  Type 'undefined' is not assignable to type 'never[]'.
  setColumns(newColumns); // error: Argument of type 'never[]' is not assignable to parameter of type 'SetStateAction<[] | undefined>'.
  Type 'never[]' is not assignable to type '[]'.
    Target allows only 0 element(s) but source may have more.
  setNames(newItems); // error: Argument of type 'IColumn[]' is not assignable to parameter of type 'SetStateAction<[] | undefined>'.
  Type 'IColumn[]' is not assignable to type '[]'.
    Target allows only 0 element(s) but source may have more.
}

export const NamesListBase: React.FunctionComponent<INamesListProps> = (
  props: INamesListProps
) => {

  var columns = buildColumns();
  const names = useSelector(selectAllNames)

  return (
    <div>
          <DetailsList
            items={names}
            columns={columns}
        />
      </div>
  ); 
}

Any pointers on how to fix these typing errors in the code?


Solution

  • There's a few things that you can do to get this working. Here's a functional code sandbox to review: https://codesandbox.io/s/cocky-morning-8hqowd?file=/src/App.tsx

    In short:

    The key is that sorting is state and, your list of items and columns need to be derived from that state in order for the interaction to work.

    Hope that helps!

    export default function App() {
      const [sortKey, setSortKey] = useState<string | undefined>(undefined);
      const [isSortedDescending, setIsSortedDescending] = useState(false);
    
      const items = [
        { age: 30, name: "Bob" },
        { age: 31, name: "Alice" }
      ];
    
      const columns: IColumn[] = [
        {
          key: "name",
          fieldName: "name",
          name: "Name",
          minWidth: 100,
          isSorted: sortKey === "name",
          isSortedDescending
        },
        {
          key: "age",
          fieldName: "age",
          name: "Age",
          minWidth: 100,
          isSorted: sortKey === "age",
          isSortedDescending
        }
      ];
    
      const sortedItems = [...items].sort((a, b) => {
        if (sortKey === "age") {
          return isSortedDescending ? b.age - a.age : a.age - b.age;
        }
        if (sortKey === "name") {
          return isSortedDescending
            ? b.name.localeCompare(a.name)
            : a.name.localeCompare(b.name);
        }
        return 0;
      });
    
      function onColumnHeaderClick(
        event?: MouseEvent<HTMLElement>,
        column?: IColumn
      ) {
        if (column) {
          setIsSortedDescending(!!column.isSorted && !column.isSortedDescending);
          setSortKey(column.key);
        }
      }
    
      return (
        <DetailsList
          onColumnHeaderClick={onColumnHeaderClick}
          columns={columns}
          items={sortedItems}
        />
      );
    }