[英]React: Issue with fetching and updating the state in useCallback
我目前正在开发一个组件,该组件进行 API 调用,检索数据,然后在 Fluent UI Datalist 中显示数据。
问题如下:组件第一次加载,然后在 API 调用后重新渲染,组件在表中显示正确的条目,其中 state.items 设置为正确的值。 但是,当我单击列运行 onColumnClick 时,function 内的项目为空,从而导致错误。 列很好,但 state.items 只是一个空集合。
如何解决这个问题,以便我看到 onColumnClick 中的项目?
这是一段代码:
export const ListComponent = (props: ListComponentProps) => {
const fetchPeople = async () => {
const entry: ITableEntry[] = [];
//items ... sdk call
for await (const item of items) {
entry.push({
key: item.id,
name: item.name,
lastName: item.lastname
});
}
}
useEffect(() => {
fetchPeople();
.then(elementList => {
setState(
state => ({ ...state, items: elementList }),
);
});
}, [])
const onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
// PLACE WHERE THE ERROR HAPPENS
console.log(items);
}, []);
const columns: IColumn[] = [
{
key: 'column1',
name: 'First 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: 'column2',
name: 'Last Name',
fieldName: 'lastname',
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,
},
];
const [state, setState] = React.useState({
items: [] as ITableEntry[],
columns: columns,
});
return (
<>
<DetailsList
items={state.items}
columns={state.columns}
/>
</>
);
});
const onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
// PLACE WHERE THE ERROR HAPPENS
console.log(items);
}, [state]);
当 state 改变时,添加对使用回调的依赖以重新计算
这是一个带有一些注释的完全重写
import React, {useCallback, useEffect, useState} from "react";
/** Option One if the function does not requires variables from the component
* itself you can put it outside like in "api" folder */
const fetchPeople = async () => {
//items ... sdk call
// if items are already calculated and they are not async
return items.map((item)=>({
key: item.id,
name: item.name,
lastName: item.lastname
}))
// else
// return (await Promise.all(items)).map((item)=>({
// key: item.id,
// name: item.name,
// lastName: item.lastname
// }))
}
export const ListComponent = (props: ListComponentProps) => {
const [items, setItems] = useState<ITableEntry[]>([])
// Option Two: use callback this function is "saved" inside a variable with a memoization based on the
// elements inside the array at the end
// const fetchPeople = useCallback(async () => {
// ...
// }, [])
useEffect(() => {
// option three you can also leave it there so it can be used in other part of the application
// const fetchPeople = async () => {
// ...
// }
// if you like async await toy can run this
(async () => {
setItems(await fetchPeople())
})()
/** if this is not modifiable you don't need to put it there
* and this function will run after the component is "mount"
* in my case fetch people will not change and that is why you should use useCallback
*/
}, [fetchPeople]);
const onColumnClick = useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
console.log(items);
}, [items]);
const columns = [
{
key: 'column1',
name: 'First 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: 'column2',
name: 'Last Name',
fieldName: 'lastname',
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,
},
]
return (
<>
<DetailsList
items={items}
columns={columns}
/>
</>
);
});
保持变量尽可能简单,除非需要一些奇怪的东西,否则只需将“数据”保存在 State
这是一个真正使这项工作有效的修复程序!
所以我实际上找到了与我的问题类似的帖子(尽管我之前已经搜索了很长时间):
但是,必须将解决方案修改为此以反映列中的更改。 该解决方案还总是在更改项目时刷新列(请参阅useEffects,我在其中设置列),因此正在更新列。
export const ListComponent = (props: ListComponentProps) => {
const [state, setState] = React.useState({
items: [] as IDocument[],
columns: [] as IColumn[],
});
const fetchPeople = React.useCallback(async () => {
const entry: ITableEntry[] = [];
//items ... sdk call
for await (const item of items) {
entry.push({
key: item.id,
name: item.name,
lastName: item.lastname
});
}
setState((state) => ({ ...state, items: elementsList }));
}, []);
useEffect(() => {
setState((state) => ({ ...state, columns: columns }));
}, [state.items]);
useEffect(() => {
fetchPeople();
}, []);
const _onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
console.log(items);
}, [state.items, state.columns]);
const columns: IColumn[] = [
{
key: 'column1',
name: 'First 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: 'column2',
name: 'Last Name',
fieldName: 'lastname',
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,
},
];
return (
<>
<DetailsList
items={state.items}
columns={state.columns}
/>
</>
);
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.