The source code is uploaded in codesandbox.
It is a DetailsList, when clicking column header, the list disappears. The reason is that columns
is []
in onColumnClick
function (there is a console.log
line).
Why setColumns
updated state successfully but cannot be retrieved in onColumnClick
function
UPDATE: I think I found the reason, it is about dependency supplied to useCallback
and useEffects
. Still dont find a way to remove cyclic dependency
import React, { useState, useEffect, useCallback } from "react";
import {
DetailsListLayoutMode,
SelectionMode,
IColumn
} from "@fluentui/react/lib/DetailsList";
import { ShimmeredDetailsList } from "@fluentui/react/lib/ShimmeredDetailsList";
export interface IPod {
name: string;
status: string;
age: number;
}
export interface IPodListState {
pods: IPod[];
}
const PodList: React.FunctionComponent = () => {
const [pods, setPods] = useState<IPod[] | undefined>(undefined);
const [columns, setColumns] = useState<IColumn[]>([]);
// sort function
type FunctionType<T> = (
items: T[],
columnKey: string,
isSortedDescending?: boolean
) => T[];
const onColumnClick = useCallback(
(ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const copyAndSort: FunctionType<IPod> = (
items,
columnKey,
isSortedDescending
) => {
const key = columnKey as keyof IPod;
return items
.slice(0)
.sort((a: IPod, b: IPod) =>
(isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1
);
};
console.log(columns); // <-- empty
const newColumns: IColumn[] = columns.slice();
const currColumn: IColumn = newColumns.filter(
(currCol) => column.key === currCol.key
)[0];
newColumns.forEach((newCol: IColumn) => {
if (newCol === currColumn) {
currColumn.isSortedDescending = !currColumn.isSortedDescending;
currColumn.isSorted = true;
} else {
newCol.isSorted = false;
newCol.isSortedDescending = true;
}
});
setColumns(newColumns);
if (pods != null)
setPods(
copyAndSort(
pods,
currColumn.fieldName!,
currColumn.isSortedDescending
)
);
},
[]
);
useEffect(() => {
setColumns([
{
key: "column1",
name: "File Type",
//className: classNames.fileIconCell,
//iconClassName: classNames.fileIconHeaderIcon,
ariaLabel:
"Column operations for File type, Press to sort on File type",
iconName: "Page",
isIconOnly: true,
fieldName: "na2me",
minWidth: 16,
maxWidth: 16,
onColumnClick: onColumnClick
//onRender: (item: IDocument) => (
// <TooltipHost content={`${item.fileType} file`}>
// <img src={item.iconName} className={classNames.fileIconImg} alt={`${item.fileType} file icon`} />
// </TooltipHost>
//),
},
{
key: "pod-name",
name: "Pod Name",
fieldName: "name",
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: "Sorted A to Z",
sortDescendingAriaLabel: "Sorted Z to A",
onColumnClick: onColumnClick,
data: "string",
isPadded: true
},
{
key: "pod-status",
name: "Status",
fieldName: "status",
minWidth: 70,
maxWidth: 90,
isResizable: true,
onColumnClick: onColumnClick,
data: "string",
//onRender: (item: IDocument) => {
// return <span>{item.dateModified}</span>;
//},
isPadded: true
},
{
key: "pod-age",
name: "Age",
fieldName: "timestamp",
minWidth: 70,
maxWidth: 90,
isResizable: true,
isCollapsible: true,
data: "number",
onColumnClick: onColumnClick,
onRender: (pod: IPod) => {
var age = "";
if (pod.age > 86400)
age = Math.floor(pod.age / 86400).toString() + " day";
else if (pod.age > 3600)
age = Math.floor(pod.age / 3600).toString() + " hour";
else if (pod.age > 60)
age = Math.floor(pod.age / 3600).toString() + "min";
else age = pod.age.toString() + " sec";
return (
<span style={{ display: "block", textAlign: "right" }}>{age}</span>
);
},
isPadded: true
},
{
key: "column5",
name: "File Size",
fieldName: "fileSizeRaw",
minWidth: 70,
maxWidth: 90,
isResizable: true,
isCollapsible: true,
data: "number",
onColumnClick: onColumnClick
//onRender: (item: IDocument) => {
// return <span>{item.fileSize}</span>;
//},
}
]);
const url = "data.json";
const fetchData = async () => {
try {
const response = await fetch(url, { mode: "cors" });
const json = await response.json();
if (Array.isArray(json)) {
setPods(json);
} else {
console.log(json);
}
} catch (error) {
console.log("error", error);
}
};
fetchData();
}, []);
/* eslint-disable no-unused-vars */
const onItemInvoked = useCallback((pod?: IPod): void => {}, []);
return (
<>
<ShimmeredDetailsList
items={pods || []}
compact={false}
columns={columns}
enableShimmer={!pods}
selectionMode={SelectionMode.none}
setKey="pods"
layoutMode={DetailsListLayoutMode.justified}
onItemInvoked={onItemInvoked}
/>
</>
);
};
export default PodList;