redux-toolkit
redux-toolkit copied to clipboard
[RED-14] Using RTK query with multiple parameters retrieved from redux
Hello there. I have recently started using RTK query to fetch and cache my data in my frontend and I have some questions. My use case is the following:
- I have some filters on my app
- When a filter changes, a new request is executed to fetch resources
- The current selection of the filters are stored in redux
- For each filter, more than one values might be selected and as a result each one of them is an array of selected values.
- I want to use RTK in order to cache the responses for each selection of filters
- I use
queryFn
instead ofquery
since I fetch data in a custom way
The api is defined as following:
export const resourcesApi = createApi({
reducerPath: 'resourcesApi',
endpoints: (builder) => ({
getResources: builder.query({
queryFn: ({selectedFilter1, selectedFilter2}) => {
// this function fetches the data
return getResourcesBasedOnFilters(selectedFilter1, selectedFilter2)
},
}),
}),
})
export const { useGetResourcesQuery } = resourcesApi
And I call it using:
export default function Resources(props){
let selectedFilter1 = useSelector((state) => state.filters.selectedFilter1)
let selectedFilter2 = useSelector((state) => state.filters.selectedFilter2)
const {isLoading, isFetching, data} = useGetResourcesQuery({selectedFilter1, selectedFilter2})
if (isLoading){
// show loader
}
else{
// show data
}
}
My questions are the following:
- The more filters I have, the slower redux becomes. Can I do something for it?
- Can I access the filters directly in queryFn ?
@kplatis Can you give an example of what you mean by "the more filters I have, the slower Redux becomes"? Ideally as a runnable CodeSandbox or a repo, with some pointers to what's happening.
Can I access the filters directly in queryFn ?
You should not, but go through the arguments - otherways we would not know when arguments update and need a new request to be made.
Hello, answering to your questions:
- The more filters I have, the slower redux becomes. Can I do something for it?
- Can I access the filters directly in queryFn ?
For filters, you can get them this way, its way simple:
const [filterOne, filterTwo] = useSelector((state) => state.filters):
you can pass the whole array as arg to use useQuery hook, like this
const {isLoading, isFetching, data} = useGetResourcesQuery(filters);
As for your question, about accessing filters directly inside queryFn
, based on docs there is a second argument in queryFn()
, called api
, it contains getState()
, so you can do:
getResources: builder.query({
queryFn: (filters, {getState} ) => {
return getResourcesBasedOnFilters(getState().filter.selectedFilter1, getState().filter.selectedFilter2)
},
}),
With this solution:
As for your question, about accessing filters directly inside
queryFn
, based on docs there is a second argument inqueryFn()
, calledapi
, it containsgetState()
, so you can do:getResources: builder.query({ queryFn: (filters, {getState} ) => { return getResourcesBasedOnFilters(getState().filter.selectedFilter1, getState().filter.selectedFilter2) }, }),
It won't refetch when the filters change right?
- When a filter changes, a new request is executed to fetch resources
It would be really nice to have some feature in the library to define dependencies like this on the API instead of in the hook. The component doesn't technically need to know anything about the "base" params of a fetch.
To clarify, the idea from my previous comment could look something like this:
getResources: builder.query({
selectFromState: (state) => {
// Use some selectors here
},
queryFn: (args, {selection} ) => {
return getResourcesBasedOnFilters(selection.filters)
},
})
Basically subscribing the endpoint to Redux state directly instead of having to go through the component.
RTKQ should merge args
and selection
for the cache key.
It might even be a good idea to use createStructuredSelector
here:
getResources: builder.query({
selectFromState: createStructuredSelector({
filters: getFilters,
}),
queryFn: (args, {selection} ) => {
return getResourcesBasedOnFilters(selection.filters)
},
})
For me this would be very helpful to work with app-wide state like auth, landing parameters, global filters.
Honestly, I am pondering about an API like this for a year now - and it would be a consequential next step.
It would add two levels of complexity:
- a significant level of type complexity
- the requirement to track those selectors and listen for state changes
We could avoid 1. by using an api like
getResources: builder.query({
// could also be used in `query`
queryFn: (args, {select} ) => {
const filters = select(getFilters)
return getResourcesBasedOnFilters(filters)
},
})
instead - that would not add any TypeScript overhead.
But from that point, we would have to even track multiple selectors. Nontheless, I think this one would be the way to go.
The bummer is: at the moment I don't have the time to implement anything like that. It's just too much going on for an endeavor of that size.
We only want to refetch when relevant state changes. I was assuming that the selection would need to be outside queryFn
(or query
) to be able to track and trigger new fetches when needed. How would that work with adding select
to the api param? (I may be missing some context about how things work under the hood)
Thanks for considering, perhaps there are "lighter" solutions, I'm just shooting from the hip from the outside here.
@GriffinSauce : one option, if perhaps a bit unwieldy, would be to add a listener middleware entry that watches the relevant bits of state using the predicate
option, and specifically triggers refetching
It won't refetch when the filters change right?
Just for clarity and to answer your question @GriffinSauce:
It will not refetch if the filters are changed, and as I see it you also lose the convenience of the skip
logic here when you implement the query using queryFn
.
This concept of adding dependencies/selectors to the endpoints would be a great addition to the library! I, unfortunately, have no clue on where to begin in implementing something like it 😅
@Vanluren filters
in that case was the argument passed in by the hooks and that will definitely cause a refetch (unless data for those new filters is already in the cache)
I'm a bit confused by the last comments @phryneas - we can already pass filters through arguments. Maybe we're missing some context here about your solution?
It will not refetch if the filters are changed
This is specifically what I'd like to have: responding to state changes without wiring it through the hook arguments.
Thanks for the comments to far 🙏
Glad I found this! I have been spinning my wheels for a while trying to figure out how to access redux state (via a selector) either IN the query method or have it passed down to it from a custom base query via meta or extraOptions but I have not been successful. It seems like what I want is not possible. I want to second @GriffinSauce 's idea to access state variables within the query method.
An example of my use case would be something simple like this:
query: (arg, { getState }) => {
const state = getState();
const userName = selectUserName(state);
// Option 1: return `/some/path/?&userName=${userName}`
// Option 2 return {
url: '/some/path/',
params: { username: userName },
};
},
I know this is not 100% correct but something to this effect. I currently have to select userName from the store in every component where I need to pass userName as an argument to a query hook. It's starting to be duplicated a lot more than I'd like and I thought it would be nice to be able to just grab it at the endpoint definition.