I'm working on a React app with Redux-Toolkit (RTK) Query. I need to fetch accounts
from the server and then render them as well derive some data from them through a chain of selectors. If I fetch them with createAsyncThunk
and save in the store manually I can easily use them in any selector. I expected similar experience if I'm using RTK Query can't figure out how it works.
If I have a query without any parameters it works nicely:
export const apiSlice = createApi({
reducerPath: 'API',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getAccounts: builder.query<Account[], void>({
query: () => 'accounts',
}),
}),
});
export const getAccountsQuery = apiSlice.endpoints.getAccounts.select();
export const getAccountsResult = createSelector(
[getAccountsQuery],
(accountsResult) => {
return accountsResult.data || [];
}
);
I can now use getAccountsResult
in any selector. However if the endpoint requires a parameter it's a different story:
export const apiSlice = createApi({
reducerPath: 'API',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getAccounts: builder.query<Account[], void>({
query: (orgId) => ({
url: 'accounts',
params: { orgId },
}),
}),
}),
});
Now I need to pass orgId
to apiSlice.endpoints.getAccounts.select()
and I can't do it inside a selector. Ideally I'd like it to be something like:
export const getAccountsResult = createSelector(
[getOrgId],
(orgId) => {
return apiSlice.endpoints.getAccounts.select(orgId).data || [];
}
);
but that's not how it works. Is there a way to achieve it?
And more broadly speaking, am I even supposed to be use RTK Query this way? There is a lot of resources on how to access and control data inside components but there's surprisingly little info on how to work with data in selectors.
apiSlice.endpoints.getAccounts.select(orgId)
creates a new selector function each time it's called, so it doesn't make for good use inline when trying to create a memoized selector function via createSelector
. You can instantiate a memoized selector function using React's useMemo
though, and use this in your useSelector
/useAppSelector
hook.
Example:
const selectAccountsByOrgId = React.useMemo(
() => apiSlice.endpoints.getAccounts.select(orgId),
[orgId],
);
const { data, ...rest } = useAppSelector(selectAccountsByOrgId);
You might not actually need to do that though when you could just use your generated useGetAccountsQuery
hook.
const { data, ...rest } = useGetAccountsQuery(orgId, /* options */);
Just make sure you are actually exporting the generated hooks:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const apiSlice = createApi({
reducerPath: 'API',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getAccounts: builder.query<Account[], string>({
query: (orgId) => ({
url: 'accounts',
params: { orgId },
}),
}),
}),
});
export const { useGetAccountsQuery } = apiSlice;
...
Since you can't use these as input selectors in createSelector
you can abstract the logic into a custom hook if necessary.
Example:
const useQueryComplexAccountByOrgId = (orgId: string) => {
const selectAccountsByOrgId = React.useMemo(
() => apiSlice.endpoints.getAccounts.select(orgId),
[orgId],
);
const { data, ...rest } = useAppSelector(selectAccountsByOrgId);
// OR
// const { data, ...rest } = useGetAccountsQuery(orgId, /* options */);
const otherSelectedStateValue = useAppSelector(selectComplexState);
...
// ... business logic
};
const result = useQueryComplexAccountByOrgId(orgId);