react-table, table 内的 useState 挂钩结果为默认值,尽管之前已设置
react-table, useState hook within table results to default value although set before
我正在使用 react-table 在 table 中显示获取的数据。您还可以在 table 中使用不同的按钮与数据进行交互,例如删除条目或更新其数据(切换按钮以批准提交的行)。
数据在初始 useEffect(() => fetchBars(), [])
中获取,然后按照 react-table 文档中的建议通过 useMemo 将其传递给 useTable。现在我可以单击 table 中前面提到的按钮来删除条目,但是当我尝试访问 fetchBars()
中设置的数据 (bars
) 时 returns useState()
使用的默认状态是一个空数组 []。我缺少什么细节?例如,我想使用条形状态来过滤已删除的行,从而使 table 具有反应性,而不必在每次更新时重新获取。
在 updateMyData()
中调用 console.log(bars)
时它会正确显示获取的数据,但是在 handleApprovedUpdate()
中调用 console.log(bars)
会产生空数组,为什么?我是否需要将 handleApprovedUpdate()
传递到单元格以及 useTable 挂钩中?
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
row: row,
updateMyData, // This is a custom function that we supplied to our table instance
}: CellValues) => {
const [value, setValue] = useState(initialValue)
const onChange = (e: any) => {
setValue(e.target.value)
}
const onBlur = () => {
updateMyData(index, id, value)
}
useEffect(() => {
setValue(initialValue)
}, [initialValue])
return <EditableInput value={value} onChange={onChange} onBlur={onBlur} />
}
const Dashboard: FC<IProps> = (props) => {
const [bars, setBars] = useState<Bar[]>([])
const [loading, setLoading] = useState(false)
const COLUMNS: any = [
{
Header: () => null,
id: 'approver',
disableSortBy: true,
Cell: (props :any) => {
return (
<input
id="approved"
name="approved"
type="checkbox"
checked={props.cell.row.original.is_approved}
onChange={() => handleApprovedUpdate(props.cell.row.original.id)}
/>
)
}
}
];
const defaultColumn = React.useMemo(
() => ({
Filter: DefaultColumnFilter,
Cell: EditableCell,
}), [])
const updateMyData = (rowIndex: any, columnId: any, value: any) => {
let barUpdate;
setBars(old =>
old.map((row, index) => {
if (index === rowIndex) {
barUpdate = {
...old[rowIndex],
[columnId]: value,
}
return barUpdate;
}
return row
})
)
if(barUpdate) updateBar(barUpdate)
}
const columns = useMemo(() => COLUMNS, []);
const data = useMemo(() => bars, [bars]);
const tableInstance = useTable({
columns: columns,
data: data,
initialState: {
},
defaultColumn,
updateMyData
}, useFilters, useSortBy, useExpanded );
const fetchBars = () => {
axios
.get("/api/allbars",
{
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
}, )
.then(response => {
setBars(response.data)
})
.catch(() => {
});
};
useEffect(() => {
fetchBars()
}, []);
const handleApprovedUpdate = (barId: number): void => {
const approvedUrl = `/api/bar/approved?id=${barId}`
setLoading(true)
axios
.put(
approvedUrl, {},
{
headers: {Authorization: "Bearer " + localStorage.getItem("token")}
}
)
.then(() => {
const updatedBar: Bar | undefined = bars.find(bar => bar.id === barId);
if(updatedBar == null) {
setLoading(false)
return;
}
updatedBar.is_approved = !updatedBar?.is_approved
setBars(bars.map(bar => (bar.id === barId ? updatedBar : bar)))
setLoading(false)
})
.catch((error) => {
setLoading(false)
renderToast(error.response.request.responseText);
});
};
const renderTable = () => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = tableInstance;
return(
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
<span {...column.getSortByToggleProps()}>
{column.render('Header')}
</span>{' '}
<span>
{column.isSorted ? column.isSortedDesc ? ' ▼' : ' ▲' : ''}
</span>
<div>{column.canFilter ? column.render('Filter') : <Spacer/>}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
const rowProps = {...row.getRowProps()}
delete rowProps.role;
return (
<React.Fragment {...rowProps}>
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
)
})}
</tr>
{row.isExpanded ? renderRowSubComponent({row}): null}
</React.Fragment>
)})
}
</tbody>
</table>
)
}
}
export default Dashboard;
您在 handleApprovedUpdate
中看到陈旧的值,因为它在第一次呈现组件时捕获 bars
,然后因为您在 COLUMNS
中使用它而永远不会更新,它被一个 useMemo
和一个空的 dependencies 数组包裹着。
这在您的示例中很难形象化,因为它通过几层间接过滤,所以这是一个人为的示例:
function MyComponent() {
const [bars, setBars] = useState([]);
const logBars = () => {
console.log(bars);
};
const memoizedLogBars = useMemo(() => logBars, []);
useEffect(() => {
setBars([1, 2, 3]);
}, []);
return (
<button onClick={memoizedLogBars}>
Click me!
</button>
);
}
单击该按钮将始终 记录[]
,即使bars
在useEffect
中立即更新为[1, 2, 3]
.当你用 useMemo
和一个空的 dependencies 数组记忆 logBars
时,你是在告诉 React “使用你当前可以看到的 bars
的值,它永远不会改变(我保证)”。
您可以通过将 bars
添加到 useMemo
的依赖项数组来解决此问题。
const memoizedLogBars = useMemo(() => logBars, [bars]);
现在,单击该按钮应该会正确记录 bars
的最新值。
在您的组件中,您应该能够通过将 columns
更改为
来解决您的问题
const columns = useMemo(() => COLUMNS, [bars]);
您可以在项目设置的挂钩 here. You may also want to consider adding eslint-plugin-react-hooks 中阅读有关陈旧值的更多信息,以便您可以自动识别此类问题。
我正在使用 react-table 在 table 中显示获取的数据。您还可以在 table 中使用不同的按钮与数据进行交互,例如删除条目或更新其数据(切换按钮以批准提交的行)。
数据在初始 useEffect(() => fetchBars(), [])
中获取,然后按照 react-table 文档中的建议通过 useMemo 将其传递给 useTable。现在我可以单击 table 中前面提到的按钮来删除条目,但是当我尝试访问 fetchBars()
中设置的数据 (bars
) 时 returns useState()
使用的默认状态是一个空数组 []。我缺少什么细节?例如,我想使用条形状态来过滤已删除的行,从而使 table 具有反应性,而不必在每次更新时重新获取。
在 updateMyData()
中调用 console.log(bars)
时它会正确显示获取的数据,但是在 handleApprovedUpdate()
中调用 console.log(bars)
会产生空数组,为什么?我是否需要将 handleApprovedUpdate()
传递到单元格以及 useTable 挂钩中?
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
row: row,
updateMyData, // This is a custom function that we supplied to our table instance
}: CellValues) => {
const [value, setValue] = useState(initialValue)
const onChange = (e: any) => {
setValue(e.target.value)
}
const onBlur = () => {
updateMyData(index, id, value)
}
useEffect(() => {
setValue(initialValue)
}, [initialValue])
return <EditableInput value={value} onChange={onChange} onBlur={onBlur} />
}
const Dashboard: FC<IProps> = (props) => {
const [bars, setBars] = useState<Bar[]>([])
const [loading, setLoading] = useState(false)
const COLUMNS: any = [
{
Header: () => null,
id: 'approver',
disableSortBy: true,
Cell: (props :any) => {
return (
<input
id="approved"
name="approved"
type="checkbox"
checked={props.cell.row.original.is_approved}
onChange={() => handleApprovedUpdate(props.cell.row.original.id)}
/>
)
}
}
];
const defaultColumn = React.useMemo(
() => ({
Filter: DefaultColumnFilter,
Cell: EditableCell,
}), [])
const updateMyData = (rowIndex: any, columnId: any, value: any) => {
let barUpdate;
setBars(old =>
old.map((row, index) => {
if (index === rowIndex) {
barUpdate = {
...old[rowIndex],
[columnId]: value,
}
return barUpdate;
}
return row
})
)
if(barUpdate) updateBar(barUpdate)
}
const columns = useMemo(() => COLUMNS, []);
const data = useMemo(() => bars, [bars]);
const tableInstance = useTable({
columns: columns,
data: data,
initialState: {
},
defaultColumn,
updateMyData
}, useFilters, useSortBy, useExpanded );
const fetchBars = () => {
axios
.get("/api/allbars",
{
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
}, )
.then(response => {
setBars(response.data)
})
.catch(() => {
});
};
useEffect(() => {
fetchBars()
}, []);
const handleApprovedUpdate = (barId: number): void => {
const approvedUrl = `/api/bar/approved?id=${barId}`
setLoading(true)
axios
.put(
approvedUrl, {},
{
headers: {Authorization: "Bearer " + localStorage.getItem("token")}
}
)
.then(() => {
const updatedBar: Bar | undefined = bars.find(bar => bar.id === barId);
if(updatedBar == null) {
setLoading(false)
return;
}
updatedBar.is_approved = !updatedBar?.is_approved
setBars(bars.map(bar => (bar.id === barId ? updatedBar : bar)))
setLoading(false)
})
.catch((error) => {
setLoading(false)
renderToast(error.response.request.responseText);
});
};
const renderTable = () => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = tableInstance;
return(
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
<span {...column.getSortByToggleProps()}>
{column.render('Header')}
</span>{' '}
<span>
{column.isSorted ? column.isSortedDesc ? ' ▼' : ' ▲' : ''}
</span>
<div>{column.canFilter ? column.render('Filter') : <Spacer/>}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
const rowProps = {...row.getRowProps()}
delete rowProps.role;
return (
<React.Fragment {...rowProps}>
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
)
})}
</tr>
{row.isExpanded ? renderRowSubComponent({row}): null}
</React.Fragment>
)})
}
</tbody>
</table>
)
}
}
export default Dashboard;
您在 handleApprovedUpdate
中看到陈旧的值,因为它在第一次呈现组件时捕获 bars
,然后因为您在 COLUMNS
中使用它而永远不会更新,它被一个 useMemo
和一个空的 dependencies 数组包裹着。
这在您的示例中很难形象化,因为它通过几层间接过滤,所以这是一个人为的示例:
function MyComponent() {
const [bars, setBars] = useState([]);
const logBars = () => {
console.log(bars);
};
const memoizedLogBars = useMemo(() => logBars, []);
useEffect(() => {
setBars([1, 2, 3]);
}, []);
return (
<button onClick={memoizedLogBars}>
Click me!
</button>
);
}
单击该按钮将始终 记录[]
,即使bars
在useEffect
中立即更新为[1, 2, 3]
.当你用 useMemo
和一个空的 dependencies 数组记忆 logBars
时,你是在告诉 React “使用你当前可以看到的 bars
的值,它永远不会改变(我保证)”。
您可以通过将 bars
添加到 useMemo
的依赖项数组来解决此问题。
const memoizedLogBars = useMemo(() => logBars, [bars]);
现在,单击该按钮应该会正确记录 bars
的最新值。
在您的组件中,您应该能够通过将 columns
更改为
const columns = useMemo(() => COLUMNS, [bars]);
您可以在项目设置的挂钩 here. You may also want to consider adding eslint-plugin-react-hooks 中阅读有关陈旧值的更多信息,以便您可以自动识别此类问题。