FluentUI DetailsList onColumnClick с хуками React дает пустые элементы
Я пытаюсь создать DetailsList с сортируемыми столбцами (аналогично примеру в документации здесь: https://uifabric.azurewebsites.net/), но вместо компонента Class я хочу использовать функциональные компоненты и крючки.
Проблема в том, что когда выполняется функция onColumnClick, массив "items" все еще пуст.
Это в основном настройка моего компонента:
const MyList= () => {
const [items, setItems] = useState([]);
const [columns, setColumns] = useState([]);
useEffect(() => {
const newItems = someFetchFunction()
setItems(newItems);
const newColumns= [
{
key: "column1",
name: "Name",
fieldName: "name",
onColumnClick: handleColumnClick,
},
{
key: "column2",
name: "Age",
fieldName: "age",
onColumnClick: handleColumnClick,
},
];
setColumns(newColumns);
}, []);
const handleColumnClick = (ev, column) => {
console.log(items); // This returns [] instead of a populated array.
};
return <DetailsList items={items} columns={columns} />;
};
Проблема в том, что в handleColumnClick
то items
возвращает пустой массив. Это очевидная проблема, когда вы хотите отсортировать элементы по определенному столбцу.
3 ответа
Я предполагаю, что причина, по которой массив элементов пуст, заключается в том, что состояние было "украдено", ознакомьтесь с этой статьей: https://css-tricks.com/dealing-with-stale-props-and-states-in-reacts-functional-components/
Если честно, не понимаю, почему так происходит, наверное,
onColumnClick
обрабатывается асинхронно?!
Однако добавление
useRef
крючок решает проблему для меня:
const MyList = () => {
const [columns, setColumns] = useState([]);
const [items, setItems] = useState([]);
const refItems = useRef(items);
const updateItems = (newItems) => {
refItems.current = newItems;
setItems(newItems);
}
useEffect(() => {
const newItems = someFetchFunction()
updateItems(newItems);
const newColumns = // ...
setColumns(newColumns);
}, []);
const handleColumnClick = (ev, column) => {
console.log(refItems.current);
};
return <DetailsList items={items} columns={columns} />;
};
Это связано с тем, что столбцы создаются только в useEffect один раз, и ваши элементы состояния закрываются внутри этого экземпляра метода onColumnClick. Следующий рендер создает новый экземпляр onColumnClick, но все столбцы имеют старый экземпляр с закрытыми старыми переменными. Простое решение для этого состоит в том, чтобы добавить в свой код при каждом рендеринге (то есть непосредственно в функции компонента) что-то вроде этого:
columns.forEach(c => c.onColumnClick = _onColumnClick);
чтобы повторно прикрепить текущий _onColumnClick ко всем столбцам.
Здесь проблема связана сclosures
. Ваш столбец является закрытием компонента (функции) MyList и фиксирует состояние переменной во время ее создания.
У useEffect пустой массив зависимостей, поэтому он запускается только один раз, присваивая функцию свойству onColumnClick с пустымitem
массив - кажется, что он пуст при первом запуске.
Решением в вашем случае может быть перемещение вашей переменной за пределы хука useEffect, так что обаhandleColumnClick
иnewItems
иметь один и тот же экземпляр на каждом рендере, например:
const [items, setItems] = useState([]);
useEffect(() => {
const newItems = someFetchFunction()
setItems(newItems);
}, []);
const handleColumnClick = (ev, column) => {
console.log(items);
};
const newColumns = [
{
key: "column1",
name: "Name",
fieldName: "name",
onColumnClick: handleColumnClick,
},
{
key: "column2",
name: "Age",
fieldName: "age",
onColumnClick: handleColumnClick,
},
];
return <DetailsList items={items} columns={newColumns} />;
Если вам не нравится идея воссозданияnewColumns
при каждом повторном рендеринге вы также можете добавить второй useEffect с[items]
зависимость для запуска только послеitems
в конечном итоге доступен в состоянии компонентов.