简体   繁体   English

反应表,表结果中的 useState 钩子到默认值,尽管之前已设置

[英]react-table, useState hook within table results to default value although set before

I am using react-table to display fetched data within a table.我正在使用 react-table 在表中显示获取的数据。 You also have different buttons within that table to interact with the data such as deleting an entry, or updating its data (toggle button to approve a submitted row).您还可以在该表中使用不同的按钮与数据交互,例如删除条目或更新其数据(切换按钮以批准提交的行)。

The data is being fetched in an initial useEffect(() => fetchBars(), []) and then being passed to useTable by passing it through useMemo as suggested in the react-table documentation.数据在初始useEffect(() => fetchBars(), [])中获取,然后按照 react-table 文档中的建议通过 useMemo 传递给 useTable。 Now I can click on the previously mentioned buttons within the table to delete an entry but when I try to access the data ( bars ) that has been set within fetchBars() it returns the default state used by useState() which is an empty array [].现在我可以单击表中前面提到的按钮来删除一个条目,但是当我尝试访问在fetchBars()中设置的数据( bars )时,它会返回 useState useState() ) 使用的默认 state ,它是一个空数组[]。 What detail am I missing?我缺少什么细节? I want to use the bars state in order to filter deleted rows for example and thus make the table reactive, without having to re-fetch on every update.例如,我想使用条形 state 来过滤已删除的行,从而使表具有反应性,而不必在每次更新时重新获取。

When calling console.log(bars) within updateMyData() it displays the fetched data correctly, however calling console.log(bars) within handleApprovedUpdate() yields to the empty array, why so?updateMyData()中调用console.log(bars)时,它会正确显示获取的数据,但是在handleApprovedUpdate()中调用console.log(bars)会产生空数组,为什么会这样? Do I need to pass the handleApprovedUpdate() into the cell as well as the useTable hook as well?我是否需要将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;

You're seeing stale values within handleApprovedUpdate because it's capturing bars the first time the component is rendered, then never being updated since you're using it inside COLUMNS , which is wrapped with a useMemo with an empty dependencies array.您会在handleApprovedUpdate中看到陈旧的值,因为它在第一次呈现组件时捕获了bars ,然后因为您在COLUMNS中使用它而永远不会更新,它被一个带有空依赖项数组的useMemo包装。

This is difficult to visualize in your example because it's filtered through a few layers of indirection, so here's a contrived example:这在您的示例中很难可视化,因为它是通过几层间接过滤的,所以这是一个人为的示例:

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

Clicking the button will always log [] , even though bars is immediately updated inside the useEffect to [1, 2, 3] .单击该按钮将始终记录[] ,即使barsuseEffect内立即更新为[1, 2, 3] When you memoize logBars with useMemo and an empty dependencies array, you're telling React "use the value of bars you can currently see, it will never change (I promise)".当你用logBars和一个空的依赖数组来useMemo时,你是在告诉 React “使用你当前可以看到的bars的值,它永远不会改变(我保证)”。

You can resolve this by adding bars to the dependency array for useMemo .您可以通过将bars添加到useMemo的依赖项数组来解决此问题。

const memoizedLogBars = useMemo(() => logBars, [bars]);

Now, clicking the button should correctly log the most recent value of bars .现在,单击按钮应正确记录bars的最新值。

In your component, you should be able to resolve your issue by changing columns to在您的组件中,您应该能够通过将columns更改为来解决您的问题

const columns = useMemo(() => COLUMNS, [bars]);

You can read more about stale values in hooks here .您可以在此处阅读有关挂钩中的陈旧值的更多信息。 You may also want to consider adding eslint-plugin-react-hooks to your project setup so you can identify issues like this automatically.您可能还需要考虑将eslint-plugin-react-hooks添加到您的项目设置中,以便您可以自动识别此类问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM