У меня есть постраничные данные с моего сервера, которые я хотел бы отображать в бесконечной прокрутке. Я знаю, что могу справиться с этим с помощью чистого Redux, но мне бы хотелось сделать это с помощью Redux Toolkit. Я пробовал читать https://github.com/reduxjs/redux-toolkit/discussions/1163?sort=new , но не понимаю, как люди это сделали. RTK merge
кажется наиболее многообещающим вариантом https://redux-toolkit.js.org/rtk-query/api/createApi#merge но пока моя реализация просто перезаписывает существующие данные новой страницей, а не добавляет их.
Код согласно примеру merge
в официальной документации:
query: ({accountId, page}) => {
console.info('accountId', accountId);
return {
url: `/api/transactions/account/${accountId}`,
method: 'get',
params: {page}
}
},
serializeQueryArgs: ({endpointName}) => {
return endpointName
},
merge: (currentCache, newItems) => {
currentCache.push(...newItems)
},
forceRefetch({ currentArg, previousArg}) {
return currentArg !== previousArg;
},
}),
выдает мне ошибку TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function
для currentCache.push(...newItems)
Я заменил это на return [...currentCache, ...newItems]
, но это говорит мне об этом TypeError: currentCache is not iterable
Что мне попробовать дальше? Данные находятся в response.data.data
, что может немного сбить с толку. Мне также нужен доступ к response.data.last_page
, чтобы знать, когда прекратить получение данных.
Я предполагаю, что вам нужно убедиться, что currentCache всегда является массивом при выполнении операции распространения. вы можете гарантировать, что currentCache, выполнив:
merge: (currentCache = [], newItems) => [...currentCache, ...newItems],
Кроме того, чтобы получить доступ к response.data.last_page
и прекратить получение данных, когда это необходимо, вам может потребоваться настроить логику вне функции слияния. Вы можете проверить значение last_page
в своем компоненте или там, где вы отправляете действие для получения данных, и условно остановить дальнейшую выборку на основе этого значения.
Вы пробовали обновить функцию слияния? Поскольку я предполагаю, что ошибка, с которой вы столкнулись, связана исключительно с типом currentCache, который получает функция, убедитесь, что это массив, если нет, попробуйте назначить пустой массив.
Также я вижу, что вы не возвращаетесь из функции merge
. Обычно вам необходимо явно использовать оператор return для получения значения из функции. Если вы не включите оператор return, функция неявно вернет return
.
Согласно документации Since this is wrapped with Immer, you may either mutate the currentCacheValue directly, or return a new value, but not both at once.
я считаю, что исходный пример currentCache.push
напрямую изменяет значение и поэтому не требует оператора возврата. Модификация, которую я пробовал, использует return
. А еще объединяйтесь Will only be called if the existing currentCacheData is not undefined - on first response, the cache entry will just save the response data directly.
Это был вопрос проб и ошибок, поскольку я никогда не мог увидеть значение currentCache
, но понял, что мне нужно добавить response.data.data
в currentCache. В итоге сработало следующее.
currentCache.data.push(...newItems.data);
Мой окончательный код:
export const transactionsSlice = apiSlice.injectEndpoints({
endpoints: build => ({
getTransactions: build.query({
query: ({accountId, page}) => {
return {
url: `/api/transactions/account/${accountId}`,
method: 'get',
params: {page}
}
},
serializeQueryArgs: ({endpointName}) => {
return endpointName
},
merge: (currentCache, newItems, { arg }) => {
const { page } = arg;
// this check deletes all transactions when account changes
if (page === 1) {
return newItems;
}
// more complex update to replace existing data if required
const updatedData = [
...currentCache.data.filter(existingItem => !newItems.data.some(newItem => newItem.trans_id === existingItem.trans_id)),
...newItems.data,
];
return {
data: updatedData,
current_page: newItems.current_page,
last_page: newItems.last_page,
};
},
forceRefetch({ currentArg, previousArg}) {
return currentArg !== previousArg;
},
providesTags: (result = [], error, arg) => [
'Transaction',
...(result.data ? result.data.map(({ id }) => ({ type: 'Transaction', id })) : []),
],
}),
deleteTransaction: build.mutation({
query: (transactionId) => {
return {
url: `/api/transactions/${transactionId}/delete`,
method: 'patch',
}
},
// update cache to reflect deleted transaction
async onQueryStarted(transactionId, { dispatch, queryFulfilled}) {
const patchResult = dispatch(
transactionsSlice.util.updateQueryData('getTransactions', transactionId, (draft) => {
const transactionToUpdate = draft.data.find(transaction => transaction.trans_id === transactionId);
transactionToUpdate.deleted = 1;
})
)
queryFulfilled.catch(patchResult.undo);
},
invalidatesTags: (result, error, arg) => [
{ type: 'Account' }
],
}),
})
})
Обратите внимание, что я включил предоставление и аннулирование тегов, потому что в моем проекте существует некоторое взаимодействие между Account
и Transaction
, но это не является основным для работы с нумерацией страниц с RTK.
Хотя это решило мою насущную проблему, я собираюсь вернуться к управлению данными самостоятельно в простом сокращении, потому что я не мог должным образом управлять изменениями. Если я обновлю элемент с самой последней страницы, то ForceRefetch обновит эту страницу, но если я обновлю элемент с более ранней страницы, мне так и не удастся обновить соответствующую страницу.
Дальнейшее обновление: после небольшого исследования я обнаружил, что вы можете вручную обновить кэшированные данные, чтобы они соответствовали обновлениям сервера, без необходимости повторной загрузки данных. redux-toolkit.js.org/rtk-query/usage/manual-cache-updates Это идеально подошло для моих нужд. Я обновлю свой ответ полным кодом.
во второй части вы правы —
transactions
проходит с данными подкачки, и я визуализирую картуtransactions.data
. К сожалению, основная проблемаcurrentCache is not iterable
остаётся.