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

单击该按钮将始终 记录[],即使barsuseEffect 中立即更新为[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 中阅读有关陈旧值的更多信息,以便您可以自动识别此类问题。