RTK Query: Multiple queries at once #1171
Replies: 21 comments 38 replies
-
This may not be 100% identical, but there was an SO question earlier today about an infinite scroll pagination scenario where it sounds like they want to keep showing existing data, but also fetch new data: |
Beta Was this translation helpful? Give feedback.
-
Yeah, Right now, you would probably need to do the logic by hand, meaning something like const runningQueries = useRef({})
useEffect(() => {
for (const [query, arg] of queries) {
if (!runningQueries.current[query]) {
runningQueries.current[query] = {}
}
if (!runningQueries.current[query][arg]) {
runningQueries.current[query][arg] = dispatch(api.endpoints[query].invoke(arg)
}
}
for (const [query, args] of Object.entries(runningQueries.current)) {
for (const [arg, promise] of Object.entries(args)) {
if (!queries.some(([q,a]) => q == query && a == arg) {
promise.unsubscribe()
}
}
}
}, queries) and then something similar with |
Beta Was this translation helpful? Give feedback.
-
For the "infinite scrolling" approach that Mark mentioned, I answered that over in StackOverflow. I don't think that will need a generalized API, as it is quite easy to implement with just three |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot for your help. I really appreciate that. I'll try to incorporate your suggestions into our project tomorrow |
Beta Was this translation helpful? Give feedback.
-
@jonathanschad if you come up with fully runnable code, it would be great if you could share that here with others - mine above is pretty much pseudocode from the back of my head, so there's definitely things to improve on it :) |
Beta Was this translation helpful? Give feedback.
-
I have to admit defeat here. I tried converting your code in something usable but I just couldn't get it to work. Unfortunately, I don't have time to deal with this problem any further. I think for my case it should be enough to manually fetch the data. But thanks again for the help you guys provided :) |
Beta Was this translation helpful? Give feedback.
-
also faced the problem of implementing endless data loading and did not find an opportunity to implement it without crutches. very sad |
Beta Was this translation helpful? Give feedback.
-
@lamo2k123 hey, the library has only been out officially for just over a week :) If you can show us examples of the specific use case you're trying to solve, we can look at providing answers or improving the API, but just saying "can't do it without crutches, very sad" isn't a response that will help us help you. |
Beta Was this translation helpful? Give feedback.
-
Need an analogue useInfiniteQuery https://react-query.tanstack.com/guides/infinite-queries I saw that now there is TODO on Infinite scrolling. Upset by the absence of Infinite scrolling Thanks for what you are doing and for developing the tools. |
Beta Was this translation helpful? Give feedback.
-
@lamo2k123 Lenz showed one possible approach for an infinite query implementation in that Stack Overflow answer: Does something like that work for you? |
Beta Was this translation helpful? Give feedback.
-
This option is not suitable in my case. I need an honest list of what has been uploaded |
Beta Was this translation helpful? Give feedback.
-
@lamo2k123 can you give more specifics? what do you mean by an "honest list"? The more details you can provide, the better we can understand what you're trying to do and what might be helpful. Having said that, I'm going to go ahead and convert this into a discussion thread, which I think will work better. |
Beta Was this translation helpful? Give feedback.
-
We should probably remove that "TODO" and add an "X" or something there. At the current point, I'm more inclined to collect several cases and add recipes for them in the docs. From all the experiences I've had with infinite fetching "solutions" of other libraries, they work very good for some cases and are an absolute pain in other cases. I'd rather have some primitives in place and documented use cases than having something that only works in a few very select cases. |
Beta Was this translation helpful? Give feedback.
-
I was wondering if there were any updates on the progress of the infinit scrolling? I do have a custom solution, but honestly wished RTK Query supported it out of the box. |
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
Hello everyone! I was looking into this issue and I managed a workaround but it is far from ideal. Disclaimer: I am very new to Redux. PremiseFor my use case, I have a list of query arguments, say a list of Pokemon names. const pokemon = ["pikachu", "zapdos", "squirtle"]; For each name, I would like to query an API, say one that gives the Pokemon's type along with other information. A few caveats:
You can imagine a scenario where there are many different Pokemon names (around 5-7). WorkaroundI call useEffect(() => {
pokemon.forEach((p) => {
const result = dispatch(
pokeAPI.endpoints.getType.initiate({
name: p,
})
);
result.unsubscribe();
});
}, [pokemon, dispatch]); I then call const selectAllPokemonType = (state, names) =>
names.map((n) => pokeAPI.endpoints.getType.select({ name: n })(state));
// inside a component
// ...
const allPokemonType = useSelector((state) =>
selectAllPokemonType(state, pokemon)
);
// compute if any pokemon share a type
// ... While this works, it updates ConclusionI would really appreciate some feedback/advice. I would also appreciate an update on the official implementation of Thank you very much for your time and for actively maintaining this awesome project. |
Beta Was this translation helpful? Give feedback.
-
Here is a snippet I used for search pagination. Hope this helps someone. Should probably make a PR to bake this into RTK at some point. Usage const queries = useMemo(() => {
return pageTokens.map((pageToken) => ({
...baseQuery,
lastKey: pageToken,
}))
}, [baseQuery, pageTokens])
const responses = useQueries(myApi.endpoints.search, queries) Hook import { useEffect, useMemo, useReducer, useRef } from 'react'
import { useDispatch } from 'react-redux'
function loadingReducer(_state, originalArgs) {
return {
isUninitialized: false,
isLoading: true,
isFetching: true,
isSuccess: false,
isError: false,
originalArgs: originalArgs,
data: undefined,
currentData: undefined,
error: undefined,
}
}
function fetchingReducer(state, originalArgs) {
return {
isUninitialized: false,
isLoading: state.data === undefined,
isFetching: true,
isSuccess: state.data !== undefined,
isError: false,
originalArgs: originalArgs,
data: state.data,
currentData: undefined,
error: undefined,
}
}
function successReducer(state, data) {
return {
isUninitialized: false,
isLoading: false,
isFetching: false,
isSuccess: true,
isError: false,
originalArgs: state.originalArgs,
data,
currentData: data,
error: undefined,
}
}
function errorReducer(state, error) {
return {
isUninitialized: false,
isLoading: false,
isFetching: false,
isSuccess: false,
isError: true,
originalArgs: state.originalArgs,
data: state.data,
currentData: undefined,
error,
}
}
function useQueryResult(originalArgs) {
const [state, setState] = useReducer(
(state, [reducer, value]) => reducer(state, value),
undefined,
() => loadingReducer(undefined, originalArgs),
)
const setStateWrapper = useMemo(
() => ({
loading(originalArgs) {
setState([loadingReducer, originalArgs])
},
fetching(originalArgs) {
setState([fetchingReducer, originalArgs])
},
success(data) {
setState([successReducer, data])
},
error(error) {
setState([errorReducer, error])
},
}),
[],
)
return [state, setStateWrapper]
}
export default function useQueries(endpoint, originalArgs = [undefined]) {
const endpointRef = useRef()
const dispatch = useDispatch()
const [queryResult, setQueryResult] = useQueryResult(originalArgs)
useEffect(() => {
let active = true
const actions = originalArgs.map((originalArg) =>
endpoint.initiate(originalArg),
)
const results = actions.map((action) => dispatch(action))
const unwrappedResults = results.map((result) => result.unwrap())
if (endpointRef.current !== endpoint) {
endpointRef.current = endpoint
setQueryResult.loading(originalArgs)
} else {
setQueryResult.fetching(originalArgs)
}
Promise.all(unwrappedResults)
.then((responses) => {
if (active) {
setQueryResult.success(responses)
}
})
.catch((errResponse) => {
if (active) {
setQueryResult.error(errResponse)
}
})
return () => {
active = false
results.forEach((result) => {
result.unsubscribe()
})
}
}, [endpoint, originalArgs, dispatch, setQueryResult])
return queryResult
} |
Beta Was this translation helpful? Give feedback.
-
Hey! I'm running into a similar issue but not in the context of infinite scrolling, which is what most of this discussion seems to be about. My use case is a simple N+1 query*—actually, two of them:
This populates an Our application uses React with Redux Toolkit, but we don't use RTK Query for these endpoints. But we miss the ergonomics, the consistent caching behavior, and the nice encapsulation of the request machinery. Is this a pattern that RTK Query can comfortably support and a use case that you would recommend? * I understand that this interface might raise some eyebrows from a performance design standpoint for traditional HTTP APIs, but in this case we're talking over a low-latency WebRTC data channel where the client can easily be updated but the server can't. We're comfortable sacrificing some wire inefficiency so that the server API can be simple and decoupled from its clients. |
Beta Was this translation helpful? Give feedback.
-
still not working multiple query at once |
Beta Was this translation helpful? Give feedback.
-
Coming back to this since I now want it again for infinite scroll reasons. I looked at the solution in the Stack Overflow post by @phryneas, but I don't see how to adapt it to a pagination API that is uses cursors rather than page numbers (since page-number-based APIs tend to run into performance issues). My API looks like this: interface ListRequest {
after?: string; // opaque cursor
}
interface ListResponse {
items: T[];
hasNextPage: boolean;
endCursor?: string;
}
// analogous to the Relay cursor connections spec, though I don't use Relay:
// https://relay.dev/graphql/connections.htm
// usage:
list()
==> { items: [t1, t2], hasNextPage: true, endCursor: "xER0A41FnkDkcUr-" }
list({ after: "xER0A41FnkDkcUr-" })
==> { items: [t3, t4], hasNextPage: true, endCursor: "ovGmijz3urFCRbo0" }
list({ after: "ovGmijz3urFCRbo0" })
==> { items: [t5], hasNextPage: false })
result = [t1, t2, t3, t4, t5] The issue is that we can't map from a scroll position to a cursor, nor is there an analogue of " I've also seen this attempt on Stack Overflow, which uses the |
Beta Was this translation helpful? Give feedback.
-
At work we need to perform an arbitrary amount of requests to fetch data from an endpoint and combine all results to show in a single table (combined table results are sorted on client). Note that our particular use-case is not "infinite querying". If our use-case was infinite query, we would reach for the upcoming "infinite query" feature. If anybody else is wrestling the "get combined results of multiple queries to same endpoint in a single place" issue and reading through these threads, here was our experience. Hope it helps. First attempt We wanted to keep things as by-the-book as possible. A "multiple queries" hook would have been the most elegant solution and is most in line with how RTKQ is typically used. React Query has a really nice "multiple queries" hook, so I was hoping for a similar solution in RTKQ. After digging through this discussion, the closest thing to a working solution with proper type-safety was @NunoCardoso 's implementation. Unfortunately, the types aren't adequate. For example, the Second attempt Since a type-safe hook approach wasn't viable (without extensive additional effort), the next thing we tried was to fetch data declaratively and write a selector that gathers all the data from RTKQ's reducer. This component setup declaratively fetches data so that it is stored in RTKQ cache. Notice we aren't actually doing anything directly with the hook result, this component exists solely to fetch data and keep in RTKQ cache when it is present in the React tree.
This is what selector would look like to grab the responses out of the RTKQ cache.
Unfortunately, the selector above is problematic. It obviously doesn't reap any of the benefits that Reselect is supposed to provide because it's subscribed to the entire application's root state. This selector will recompute any time the state changes anywhere in the entire application. As far as I can tell, there is no type-safe way to improve the selector above to scope closer into the piece of state we are interested in (RTKQ's cache entries). The problem is that RTKQ's endpoint Third attempt After giving up on the last approach, we decided to call it a day and store the responses in a manually-maintained RTK slice. Here is how we grab the data and put it into our slice.
Here is some code from the slice itself:
And this is the reducer to put responses into our slice as they come in:
This obviously isn't ideal because we lose out on many benefits of RTKQ and now have a mirrored source of truth for fetched data. I don't feel great about the final result and hope we get first-class useQueries support at some point in the future 🙏 |
Beta Was this translation helpful? Give feedback.
-
Today I played around a bit with RTK Query and I was wondering if there is a way to create multiple queries at once.
The usecase for our application is the following: We have a list of larger documents(could be easily < 10mb per document) that we dynamically load from an API. These documents are needed in many different places in the application. However, the length of the list is dynamic, which means we don't know how many API requests need to be sent at the time of writing the code. Also combining the individual requests into one large request is unfortunately not possible due to the size of the data.
React-Query offers a seemingly suitable solution for this with the hook useQueries.
Another possibility we have thought about would be to write the loaded data separately into the reduxstore and then subscribe to it using useSelector. However, this does not seem to me to be an acceptable solution, as it also unnecessarily inflates the store.
Hence my question if RTK Query also offers a solution for this problem or what would be the best approach in this case?
Beta Was this translation helpful? Give feedback.
All reactions