我有一个反应钩子useDbReadTable
,用于从接受tablename
和query
初始数据的数据库中读取数据。 它返回一个对象,该对象除了包含数据库中的数据外,还包含isLoading
状态。
我想将这个钩子包装在一个新的钩子中,该钩子接受{ tablename, query }
数组的初始数据,并返回一个对象,其中包含每个表的数据库中的数据,但isLoading
状态根据新钩子中的逻辑合并为单个布尔值。
这个想法是,新钩子的调用者可以从多个表中请求数据,但只需要检查一个状态值。
我的想法是让新钩子看起来像这样,
编辑:更新的代码(我粘贴了错误的版本(
export const useDbRead = tableReads => {
let myState = {};
for (let i = 0; i < tableReads.length; ++i) {
const { tablename, query = {} } = tableReads[i];
const [{ isLoading, isDbError, dbError, data }] = useDbReadTable(tablename, query);
myState = { ...myState, [tablename]: { isLoading, isDbError, dbError, data }};
}
const finalState = {
...myState,
isLoading: Object.values(myState).reduce((acc, t) => acc || t.isLoading, false),
};
return [finalState];
};
但是,eslint 在我的useDbReadTable
电话中给了我这个错误:
React Hook "useDbReadTable" 可以执行多次。可能是因为它是在循环中调用的。在每个组件渲染中,必须以完全相同的顺序调用 React Hooks。 反应钩子/钩子规则
钩子规则说,
仅在顶层调用挂钩
不要在循环、条件或嵌套函数中调用 Hook。相反,始终在 React 函数的顶层使用 Hooks。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。这就是允许 React 在多个 useState 和 useEffect 调用之间正确保留 Hooks 状态的原因。(如果您好奇,我们将在下面深入解释。
阅读规则和解释后,似乎唯一的问题是确保在所有重新渲染时以相同的顺序调用钩子。 只要我确保传递给新钩子的表列表永远不会改变,我的新钩子不应该正常工作(正如我的初始测试所示(吗? 还是我错过了什么?
更重要的是,有没有更好的主意来实现这一点,不违反钩子规则?
编辑2:如果有帮助,这里是useDbReadTable
。 请注意,它包含的功能比我在问题中提到的要多,因为我想让问题尽可能简单。 我的问题是我的useDbRead
是否是一个很好的解决方案,或者是否有一种在不违反钩子规则的情况下做到这一点的好方法?
export const useDbReadTable = (initialTableName, initialQuery = {}, initialData = []) => {
const dbChangeFlag = useSelector(({appState}) => appState.dbChangeFlag);
const [tableName, setTableName] = useState(initialTableName);
const [query, setQuery] = useState(initialQuery);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isDbError: false,
dbError: {},
data: initialData,
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: dataFetch.FETCH_INIT });
try {
const result = Array.isArray(query) ?
await db[tableName].batchGet(query) // query is an array of Ids
:
await db[tableName].find(query);
if (!didCancel) {
dispatch({ type: dataFetch.FETCH_SUCCESS, payload: result });
}
} catch (error) {
if (!didCancel) {
dispatch({ type: dataFetch.FETCH_FAILURE, payload: error });
}
}
};
fetchData().then(); // .then() gets rid of eslint warning
return () => {
didCancel = true;
};
}, [query, tableName, dbChangeFlag]);
return [state, setQuery, setTableName];
};
您可以通过使useDbRead
本身能够识别数组来避免使用该useDbReadSingle
。像这样:
export const useDbRead = tableReads => {
const [loading, setLoading] = useState(true);
useEffect(() => {
const doIt = async () => {
// you would also need to handle the error case, but you get the idea
const data = await Promise.all(
tableReads.map(tr => {
return mydbfn(tr);
})
);
setLoading(false);
};
doIt();
}, [tableReads]);
return { loading, data };
};
当您需要将其用于单表读取时,只需使用具有单个元素的数组调用它即可。
const {loading, data: [d]} = useDbRead([mytableread])