在组件内使用 useState 时,React Table v7 未填充数据

React Table v7 not populating with data when using useState within the component

编辑:底部的最终解决方案。

我正在尝试使用 React Table v7 构建一个简单的 web-app 来显示存储在 firestore 数据库中的一些数据。一般来说,我对 React 和 javascript 还很陌生,所以如果我使用了错误的术语,请原谅我。

起初我将获取数据的函数放在 App.jsx 中,将其传递给 setState 状态 useEffect 挂钩,并且它工作正常。

然后一位同事建议将数据传递到组件状态而不是应用程序状态是一个很好的做法,这就是问题开始出现的地方。

截至目前,我无法填充 table。 header 得到渲染,但没有别的,我可以让它显示数据的唯一方法是在 Table.jsx 中做一个小的更改,而 npm start 是 运行(例如添加或更改任何 console.log) 的输出并保存文件。只有这样,才会显示数据。

我已经尝试了大约 2 天我能想到的一切(我最后尝试的是将 Table.jsx 包装到另一个组件中,但没有任何改变)。

我尝试了 console.log 所有涉及数据的步骤来尝试调试它,但我无法理解问题出在哪里。这是应用首次加载时的输出:

我目前的代码:

const parseData = async (db) => {
    const dataArray = [];
    const snapshot = db.collection('collection-name').get();

    snapshot.then(
        (querySnapshot) => {
            querySnapshot.forEach((doc) => {
                const document = { ...doc.data(), id: doc.id };
                dataArray.push(document);
            });
        },
    );
    console.log('func output', dataArray);
    return dataArray;
};

export default parseData;
import { useTable } from 'react-table';

const Table = ({ columns, data }) => {
    const tableInstance = useTable({ columns, data });
    console.log('table component data received', data);
    const {
        getTableProps,
        getTableBodyProps,
        headerGroups,
        rows,
        prepareRow,
    } = tableInstance;

    return (
        // html boilerplate from https://react-table.tanstack.com/docs/quick-start#applying-the-table-instance-to-markup
    );
};

export default Table;
import { useState, useEffect, useMemo } from 'react';
import parseData from '../utils';
import Table from './Table';

const TableContainer = ({ db }) => {
    const [data, setData] = useState([]);

    useEffect(() => {
        const getData = async () => {
            const dataFromServer = await parseData(db);
            setData(dataFromServer);
            console.log('container-useEffect', dataFromServer);
        };
        getData();
    }, [db]);

    const columns = useMemo(() => [
        {
            Header: 'ID',
            accessor: 'id',
        },
        // etc...
    ], []);

    console.log('container data', data);

    return (
        <>
            <Table columns={columns} data={data} />
        </>
    );
};

export default TableContainer;
import firebase from 'firebase/app';
import 'firebase/firestore';

import TableContainer from './components/TableContainer';
import Navbar from './components/Navbar';

// ############ INIT FIRESTORE DB
const firestoreCreds = {
    apiKey: process.env.REACT_APP_API_KEY,
    authDomain: process.env.REACT_APP_AUTH_DOMAIN,
    projectId: process.env.REACT_APP_PROJECT_ID,
};
if (!firebase.apps.length) {
    firebase.initializeApp(firestoreCreds);
}
const db = firebase.firestore();

function App() {
    return (
        <div>
            <Navbar title="This is madness" />
            <div>
                <TableContainer db={db} />
            </div>
        </div>
    );
}

export default App;

编辑:最后,使用下面的建议,这是我能想出的最终解决方案。不得不将 API 调用包装在 useEffect 中的异步函数中,这让我很恼火,所以这就是我所做的。

const parseData = (db) => {
    const snapshot = db.collection('collection_name').get();

    return snapshot.then(
        (querySnapshot) => {
            const dataArray = [];
            querySnapshot.forEach((doc) => {
                const document = { ...doc.data(), id: doc.id };
                dataArray.push(document);
            });
            return dataArray;
        },
    );
};

export default parseData;

这里我还在 useEffect 中添加了标志 didCancel 以避免竞争条件,根据 this and this 这似乎是一个最佳实践。

// imports

const TableContainer = ({ db }) => {
    const [data, setData] = useState([]);

    useEffect(() => {
    let didCancel = false;

        parseData(db)
            .then((dataFromServer) => (!didCancel && setData(dataFromServer)));

    return () => { didCancel = true; }
    }, [db]);

// ...

在您的 Table 容器中,您使用空数组初始化 data。在您完成从服务器获取 data 之前,它会被发送到 Table。如果您不希望这种情况发生,您应该将默认值(在 useState 中)更改为 false 之类的内容并明确处理这种情况(例如,如果 data === false 则显示“请稍候。正在加载”)。

parseData 函数中,这一行 return dataArray; 在解析快照之前执行。您需要将 parseData 和 return 更改为 Promise 并在数据准备就绪时解决:

const parseData = async (db) => {
    const dataArray = [];
    const snapshot = db.collection('collection-name').get();
    return new Promise(resolve => {
       snapshot.then(
        (querySnapshot) => {
            querySnapshot.forEach((doc) => {
                const document = { ...doc.data(), id: doc.id };
                dataArray.push(document);
            });
            resolve(dataArray); //--> resolve when data is ready
        },
    );
    })
   
};

parseData 函数中的异步性是完全错误的(实现有点过于复杂...)。像这样重新实现它...

const parseData = async (db) => {
    // Note `await` here.
    const snapshot = await db.collection('collection-name').get();
    return snapshot.map((doc) => ({ ...doc.data(), id: doc.id }));
};

export default parseData;