I'm building a project using Redux Toolkit and RTK Query and trying to get some entries from the API. I'm using the normalize data method of createEntityAdapter because in a certain component I need the data as an array, so I ended up using a selector. Now my problem is that since I added the filter as a parameter of the query, my selector stopped working.
I looked into similar questions here like: "How to use RTK query selector with parameters?", but I'm too stupid to understand what I should modify. I'm trying to understand the RTK query documentation, but I can't.
From the question above, I know that my selector also needs to have parameters in order to know exactly what to select, and that this is not a recommended pattern, but I can't understand how to make it work.
My entry slice:
import { createSelector, createEntityAdapter } from '@reduxjs/toolkit' import { apiSlice } from './apiSlice' const entryAdapter = createEntityAdapter() const initialState = entryAdapter.getInitialState({ ids: [], entities: {}, }) export const entryApiSlice = apiSlice.injectEndpoints({ endpoints: (builder) => ({ initialState, getEntry: builder.query({ query: (filters) => ({ url: '/history', params: filters, }), transformResponse: (responseData) => { return entryAdapter.setAll(initialState, responseData) }, providesTags: (result, error, arg) => [ { type: 'Entry', id: 'LIST' }, ...result.ids.map((id) => ({ type: 'Entry', id })), ], }), addEntry: builder.mutation({ query: (data) => ({ url: '/history/new', method: 'POST', body: data, }), invalidatesTags: [{ type: 'Entry', id: 'LIST' }], }), updateEntry: builder.mutation({ query: (initialEntry) => ({ url: `/history/${initialEntry.Id}`, method: 'PUT', body: { ...initialEntry, date: new Date().toISOString(), }, }), invalidatesTags: (result, error, arg) => [{ type: 'Entry', id: arg.id }], }), deleteEntry: builder.mutation({ query: ({ id }) => ({ url: `/history/${id}`, method: 'DELETE', body: { id }, }), invalidatesTags: (result, error, arg) => [{ type: 'Entry', id: arg.id }], }), }), }) export const { useGetEntryQuery, useAddEntryMutation, useUpdateEntryMutation, useDeleteEntryMutation, } = entryApiSlice export const selectEntryResult = (state, params) => entryApiSlice.endpoints.getEntry.select(params)(state).data const entrySelectors = entryAdapter.getSelectors( (state) => selectEntryResult(state) ?? initialState ) export const selectEntry = entrySelectors.selectAll
I use this in an Entries component like this
const { data: entriesData = [], refetch, isLoading, isSuccess, isError, error, } = useGetEntryQuery(filters) const entries = useSelector(selectEntry)
Note: If I remove the "filter" from the get query, everything works as before (as expected, of course).
Disclaimer: I have no idea what the hell I'm doing, I've read the documentation and am trying to figure it out, so any feedback is greatly appreciated. Thanks!
Yes, this is a somewhat sensitive topic, as RTKQ's documentation tends to show the simplest examples, queries that don't use any parameters at all. I've had a lot of problems myself.
Anyway, you have declared
TheselectEntryResult
as a function with two parameters: state and params. Then, when you create the adapter selector underneath it, you call it with only one parameter: state. Also, when you use the final selector in your component like this:parameters are nowhere to be found, they are
undefined by default
and no data associated with such query parameters can be found.The key thing to understand here is that you actually need to pass the query parameters through each level of the selector (each wrapper) somehow.
One way here is to "forward" the parameters through the selector:
Here we use the
createSelector
function exported from RTK. Then in your component you would do something like this:This works when using the
selectAll
selector created by the entity adapter, but causes problems when usingselectById
since that selector is also parameterized. In short, theselectById
selector is internally defined to take a second argument of the entity id you wish to retrieve, whereas the method I showed uses the second argument to pass the query parameters (your filter filter in ). case).As far as I know, there is no solution so far that works perfectly and covers all of the following:
Another approach might be to create some selector factories that dynamically create base selectors for specific combinations of query parameters.
I once made such a wrapper that can be used in all situations. Unfortunately I can't share it because it's a private package, but the basic idea is to change the parameters so that both
selectById
andselectAll
(and all other selectors) work correctly , by passing the query parameters as the third parameter to the selector, and then further rewrapping each entity adapter selector:I know this sounds complicated and I've barely gotten it to work, so try to avoid going in this direction :)
A helpful article I found along the way can be found here They also describe some ways of creating selectors at the component level and remembering them, but I haven't tried them all yet. Have a look, maybe you'll find an easier way to solve your specific problem.