Home > other >  fluent ui - In details list there is loss of selection state if state is modified inside Selection o
fluent ui - In details list there is loss of selection state if state is modified inside Selection o

Time:08-01

While using fluent UI's details list, I am setting both setKey and getKey while overriding the on selection changed method. But on the double of the row or on Item Changed, the correct selected row item is not passed. Please advise.

Edited: I went ahead and create a sample of it in codesandbox and here is the link https://codesandbox.io/s/focused-matsumoto-cbwg7o?file=/src/App.js. The details list has groups in them. When I double click/ onItemInvoked on any row, it console logs correct fruit name says Berries. But the problem is when I collapsed any category says ‘Professional Items’ and then double click on a row for item ‘Mango’ in say category ‘Certifications’, it does not console logs ‘Mango’ instead all the groups get auto expanded and Berries in Professional Items category is console logged. Not sure what I am missing. Any idea greatly appreciated.

< DetailsList
columns = {
  PROTOCOL_TABLE_COLUMNS()
}
items = {
  dealProtocolSortedList
}
groups = {
  getGroups(dealProtocolSortedList)
}
groupProps = {
  {
    showEmptyGroups: true
  }
}
checkboxVisibility = {
  CheckboxVisibility.always
}
onItemInvoked = {
  onItemInvoked
}
selection = {
  selection
}
selectionPreservedOnEmptyClick = {
  true
}
setKey = {
  "example"
}
/>

const selection: any = new Selection < IDealProtocolAsset > ({
  onSelectionChanged: () => {
    const currentSelection = selection.getSelection();
    setSelectedItems(currentSelection);

    if (currentSelection.length === 1) {
      currentSelection.map((i: IDealProtocolAsset) => {
        setAssignmentProtocol(i);
        setAsgmtProtoForPrimaryOrSecondaryAsset(i);
        setProtocolNotes(i.assignmentProtocolNote);
      });
    }
  },
  // This method doesn't provide any functionality, but it is required if the type of Selection
  // does not have a 'key' attribute. If it is removed, it will throw a compile time error.
  getKey: () => {
    return Math.random() * 10000   1;
  },
});

CodePudding user response:

Incorrect key

The problem is you are using random number for the getKey and also that setKey isnt set to a valid property available within each item in the items array. This id must be stable -- which means for a given item in the list passed to it, it always returns the same value every time. Additionally, said value must be unique amongst all the items for each item.

What is happening is internally, fluent UI is storing the result of running getKey over the selected item as a way of identifying the selected item in the items list for further processing down the line, such as if to display the tick or not. But when that changes on each render, it can no longer do so.

By setting setKey and getKey to use the id field in each item, the problem is resolved.

Here's the working codesandbox.

Note when you select an item that exists in multiple categories, each variation of it is also selected. I'm unsure if that's desirable or if you'd rather it be individual, let me know in comments. Obviously its also test data, so unsure if its a realistic thing to happen anyway in final data.

Can't untick items, and collapsible state lost

To be honest the way Fluent is managing the state is really weird and quite archaic. There's 3 problems:

  • The items are defined in render, and so recreated on each render. This seems to trigger some internal state reset. We can simply move the items outside of component render so they are referentially stable.
  • The selection state also needs to be memoized such that it also is not recreated on every render.
  • The groups also need to be memoized or they are recreated on each render and they loose their collapsed state.
import "./styles.css";
import {
  DetailsList,
  Selection,
  SelectionMode,
  CheckboxVisibility
} from "@fluentui/react";
import React, { useMemo, useState } from "react";

const items = [
  { categoryName: "Certifications", id: 1, name: "Apple" },
  { categoryName: "Health Items", id: 2, name: "Mango" },
  { categoryName: "Professional Items", id: 3, name: "Berries" },
  { categoryName: "Professional Items", id: 4, name: "Banana" },
  { categoryName: "Certifications", id: 5, name: "Pappaya" }
];

export default function App() {
  const [, setSelectedItems] = useState();

  const groups = useMemo(() => getGroups(items), []);

  const selection = useMemo(
    () =>
      new Selection({
        onSelectionChanged: () => {
          setSelectedItems(selection.getSelection());
        },
        selectionMode: SelectionMode.single,
        getKey: (item) => item.id
      }),
    []
  );

  const onItemInvoked = async (item) => {
    console.log("Inside onItemInvoked");
    console.log(item.name);
  };
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <DetailsList
        items={items}
        selection={selection}
        groups={groups}
        checkboxVisibility={CheckboxVisibility.always}
        columns={PROTOCOL_TABLE_COLUMNS()}
        groupProps={{ showEmptyGroups: true }}
        onItemInvoked={onItemInvoked}
        selectionPreservedOnEmptyClick={true}
        setKey="id"
      />
    </div>
  );
}

export function getGroups(protocolList) {
  const groupNames = [
    "Professional Items",
    "Certifications",
    "Licensure & IDs",
    "Background Checks",
    "Health Items",
    "Competency Testing",
    "Client Paperwork",
    "Uncategorized"
  ];

  const groups = [];

  groupNames.forEach((gn) => {
    // Count group items
    const groupLength = protocolList?.filter(
      (item) => item["categoryName"] === gn
    ).length;
    // Find the first group index
    const groupIndex = protocolList
      ?.map((item) => item["categoryName"])
      .indexOf(gn);
    // Generate a group object
    groups.push({
      key: gn,
      name: gn,
      count: groupLength,
      startIndex: groupIndex,

      isCollapsed: true
    });
  });

  return groups;
}

export const PROTOCOL_TABLE_COLUMNS = () => {
  return [
    {
      data: "string",
      key: "id",
      name: "ID",
      fieldName: "id",
      minWidth: 2,
      maxWidth: 2,
      isPadded: true,
      isMultiline: true
    },
    {
      data: "string",
      key: "name",
      name: "Name",
      fieldName: "name",
      minWidth: 2,
      maxWidth: 2,
      isPadded: true,
      isMultiline: true
    }
  ];
};

  • Related