使用React.useMemo向react-table传递数组时,其中一个useMemo返回空数组导致useTable报错的问题求助
看起来你遇到的核心问题是headerColumns的useMemo依赖配置错误,加上React的渲染时序特性,导致useTable拿到了空的列配置数组。让我们一步步拆解并解决:
问题根源分析
你当前的headerColumns是基于inputDataState生成的,但useMemo的依赖却是inputNames:
const headerColumns = React.useMemo(() => header, [inputNames]);
而inputDataState是在useEffect中更新的(依赖inputNames),useEffect的执行时机是在组件渲染完成之后。这就导致:当inputNames更新时,useMemo会先触发计算(此时inputDataState还没更新,header还是旧的空数组),之后useEffect才会更新inputDataState,但这时候headerColumns已经被传给useTable了,自然就拿到了空数组。
另外,colArrayData能正常工作是因为它直接依赖data,而data的处理逻辑没有经过useEffect,是同步计算的。
具体解决方案
1. 修正useMemo的依赖项 & 内联列生成逻辑
把header的生成逻辑移到useMemo内部,同时将依赖改为inputDataState——因为列配置完全由inputDataState决定:
const headerColumns = React.useMemo(() => { return inputDataState?.map((item, i) => { return { Header: item.name, accessor: `col${i}` }; }) || []; // 兜底空数组,避免undefined }, [inputDataState]);
这样只有当inputDataState更新时,headerColumns才会重新计算,确保拿到的是最新的列配置。
2. 给useTable的配置添加兜底值
即使做了上面的修改,在初始渲染时inputDataState可能还是空数组,所以给useTable的配置加上默认值,避免触发“Cannot read property of undefined”错误:
const tableInstance = useTable({ headerColumns: headerColumns || [], colArrayData: colArrayData || [] });
3. 确保数据就绪后再初始化表格(可选但推荐)
因为你的组件中有多个异步数据请求(useSWR、useQuery),可以统一判断所有数据是否加载完成,再渲染表格相关的逻辑:
// 统一判断加载状态 const isAllLoaded = !loading && !!data && !!inputDataState.length; if (!isAllLoaded) return <p>Loading</p>; // 只有当所有数据就绪后,才初始化useTable const tableInstance = useTable({ headerColumns, colArrayData });
修改后的完整代码片段
const fetchWithId = (url, id) => fetch(`${url}?id=${id}`).then((r) => r.json()); const fetcher = (url) => fetch(url).then((r) => r.json()); export default function FormData() { const { data: cookieData, error: cookieError } = useSWR("/api/cookie", fetcher); const Router = useRouter(); const { data, error } = useSWR([`/api/data`, Router.query.id], fetchWithId); const test = `Bearer ${cookieData}`; // 用const代替var,更符合React规范 const { loading, error: inputError, data: inputNames } = useQuery( INPUT_VALUES, { variables: { id: Router.query.id }, context: { headers: { authorization: test }, }, skip: !test, } ); const [inputDataState, setInputDataState] = useState([]); useEffect(() => { if (inputNames?.findFormByID?.formInputVals?.data) { const inputData = inputNames.findFormByID.formInputVals.data; const arr = [...inputData, { name: "Sheet Add" }]; setInputDataState(arr); } }, [inputNames]); // 修正后的headerColumns useMemo const headerColumns = React.useMemo(() => { return inputDataState?.map((item, i) => { return { Header: item.name, accessor: `col${i}` }; }) || []; }, [inputDataState]); const colArrayData = React.useMemo(() => { if (!data?.data) return []; const well = data.data.map((item, i) => { delete item.data["FormID"]; delete item.data["user"]; delete item.data["visible"]; delete item.data["sheetName"]; item.data["refId"] = item.ref["@ref"].id; const ok = Object.values(item.data); return ok.map((value, i) => { return { [`col${i}`]: value }; }); }); return well?.map((a) => Object.assign({}, ...a)) || []; }, [data]); // 统一判断加载状态 const isAllLoaded = !loading && !!data && inputDataState.length > 0; if (!isAllLoaded) return <p>Loading</p>; // 数据就绪后再初始化表格 const tableInstance = useTable({ headerColumns, colArrayData }); return ( <Layout> <h1>{inputNames?.findFormByID.name}</h1> {/* 这里可以添加表格的渲染逻辑,比如用tableInstance的getTableProps等 */} </Layout> ); }
额外优化建议
- 避免使用
var,改用const/let,符合现代JS和React的编码规范; - 在
useEffect中添加空值判断,避免inputNames未就绪时访问findFormByID导致报错; - 把
colArrayData的处理逻辑也移到useMemo内部,让数据处理和缓存逻辑更集中。
内容的提问来源于stack exchange,提问作者Anders Kitson

