У меня есть ловушка реагирования useDbReadTable для чтения данных из базы данных, которая принимает начальные данные имени tablename и query . Он возвращает объект, который имеет статус isLoading в дополнение к данным из базы данных.

Я хочу обернуть этот хук в новый хук, который принимает начальные данные массива { tablename, query } и возвращает объект с данными из базы данных для каждой таблицы, но с состояниями isLoading объединенными в один логический тип на основе логика в моем новом крючке.

Идея в том, что вызывающий новый хук может запросить данные из нескольких таблиц, но должен проверить только одно значение состояния.

Я думал, что новый крючок будет выглядеть примерно так:

РЕДАКТИРОВАТЬ: обновленный код (я вставил не ту версию)

 export const useDbRead = tableReads ={amp}gt; { let myState = {}; for (let i = 0; i {amp}lt; 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) ={amp}gt; acc || t.isLoading, false), }; return [finalState]; }; 

Тем не менее, eslint дает мне эту ошибку при моем вызове useDbReadTable :

React Hook «useDbReadTable» может быть выполнен более одного раза. Возможно, потому что это вызывается в цикле. React Hooks должны вызываться в том же порядке в каждом компоненте рендера. реагирующие-крюки / правила-оф-крючков

И Правила для Крюков говорит,

Только Call Hooks на верхнем уровне

Не вызывайте Hooks внутри циклов, условий или вложенных функций. Вместо этого всегда используйте Hooks на верхнем уровне вашей функции React. Следуя этому правилу, вы гарантируете, что хуки вызываются в одном и том же порядке каждый раз при рендеринге компонента. Это то, что позволяет React правильно сохранять состояние хуков между несколькими вызовами useState и useEffect. (Если вам интересно, мы объясним это подробно ниже.)

После прочтения правила и объяснения, кажется, единственная проблема заключается в том, чтобы убедиться, что хуки вызываются в одном и том же порядке для всех повторных рендеров. Пока я гарантирую, что список таблиц, которые я передаю моему новому хуку, никогда не изменится, разве мой новый хук не должен работать нормально (как показывают мои первоначальные тесты)? Или я что-то упустил?

Что еще более важно, есть ли лучшая идея, как реализовать это, которое не нарушает Правила Крюков?

Edit2: в случае, если это полезно, вот useDbReadTable . Обратите внимание, что он включает в себя больше функциональных возможностей, чем я упоминаю в своем вопросе, поскольку я хотел сделать вопрос как можно более простым. Мой вопрос заключается в том, является ли мой useDbRead хорошим решением или есть хороший способ сделать это, не нарушая Правила Хуков?

 export const useDbReadTable = (initialTableName, initialQuery = {}, initialData = []) ={amp}gt; { const dbChangeFlag = useSelector(({appState}) ={amp}gt; 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(() ={amp}gt; { let didCancel = false; const fetchData = async () ={amp}gt; { 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 () ={amp}gt; { didCancel = true; }; }, [query, tableName, dbChangeFlag]); return [state, setQuery, setTableName]; }; , а export const useDbReadTable = (initialTableName, initialQuery = {}, initialData = []) ={amp}gt; { const dbChangeFlag = useSelector(({appState}) ={amp}gt; 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(() ={amp}gt; { let didCancel = false; const fetchData = async () ={amp}gt; { 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 () ={amp}gt; { didCancel = true; }; }, [query, tableName, dbChangeFlag]); return [state, setQuery, setTableName]; }; 

Вероятно, вы можете избежать использования useDbReadSingle если useDbRead сам массив useDbRead . Что-то вроде:

 export const useDbRead = tableReads ={amp}gt; { const [loading, setLoading] = useState(true); useEffect(() ={amp}gt; { const doIt = async () ={amp}gt; { // you would also need to handle the error case, but you get the idea const data = await Promise.all( tableReads.map(tr ={amp}gt; { return mydbfn(tr); }) ); setLoading(false); }; doIt(); }, [tableReads]); return { loading, data }; }; 

Когда вам нужно использовать его для чтения одной таблицы, просто вызовите это с массивом, который имеет один элемент.

 const {loading, data: [d]} = useDbRead([mytableread])