1

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;
  },
});
Rose
  • 13
  • 4
  • Your question needs the code added -- please see https://stackoverflow.com/help/how-to-ask under "Help others reproduce the problem" – adsy Jul 30 '22 at 18:22
  • I have added the code now. – Rose Jul 30 '22 at 19:37
  • I think we need to see a lot of the code around what you have pasted. Could you edit the post with the whole component? Sorry I know that seems burdensome, but the context really matters -- I will then try to repro and give a helping hand, its common on stack overflow for the question author to assume the problem is in one area but later its figured out its somewhere closeby. In particular, looking to see how the selected item is passed to `DetailList`, and/or where `selection` is sourced from. – adsy Jul 30 '22 at 20:53
  • 1
    Hello Adam, 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 – Rose Jul 31 '22 at 04:43

1 Answers1

1

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
    }
  ];
};

adsy
  • 8,531
  • 2
  • 20
  • 31
  • Thanks for the response, Adam. Setting the same unique on getKey and setKey makes complete sense. But my problem is when onItemInvoked or onSelectionChanged is called, all the groups expand automatically, detailslist does not maintain the previous state.????Also I will not have same id/fruits in two different groups. Another issue is I select a row and after I reclick on the same row , it does not deselect the row. – Rose Jul 31 '22 at 16:37
  • Got it, give me some minutes I think I know why about the groups, hopefully. Also can look at the thing with same item in more than 1 section. – adsy Jul 31 '22 at 17:22
  • Oh sorry I see you dont need that. Ive edited answer to fix more of the issues but theres still an issue with Mango ending up in wrong group. This is more involved...will need a bit more time for that one – adsy Jul 31 '22 at 18:16
  • 1
    Wow..works like a charm after using useMemo. Thank you!!!!!! I found a fix for the Mango ending up in the wrong group. Added sorting on items array before passing it to getGroups and that did the trick. – Rose Jul 31 '22 at 19:16
  • Good to hear! The ordering thing makes a lot of sense. – adsy Jul 31 '22 at 20:05