I have created multiple RTK Query API services split into multiple files. For this issue I have two services: "Contracts" and "Properties". The contracts service should be able to invalidate the Properties cache when a contract updates, but even after supplying the "Properties" tag to the Contracts service - the cache is not invalidated.
Here is my setup:
Properties:
export const propertyApi = createApi({
reducerPath: 'propertyApi',
baseQuery: fetchBaseQuery({ baseUrl: `${API_BASE_URL}/properties` }),
tagTypes: ['Properties'],
endpoints: builder => ({
// many endpoints
})
})
export const {
// many hooks
} = propertyApi
Contracts:
export const contractApi = createApi({
reducerPath: 'contractApi',
baseQuery: fetchBaseQuery({ baseUrl: `${API_BASE_URL}/contracts` }),
tagTypes: ['Contracts', 'Properties'],
endpoints: builder => ({
// ...
modifyContract: builder.mutation<Contract, { contract: Partial<ContractDto>, contractId: Contract['id'], propertyId: Property['id'] }>({
query: ({ contract, contractId }) => {
return {
url: `/${contractId}`,
method: 'PATCH',
credentials: "include",
body: contract
}
},
// to my understanding, this should invalidate the property cache for the property with 'propertyId', but it doesn't seem to work
invalidatesTags: (_res, _err, { propertyId }) => ['Properties', 'Contracts', { type: 'Properties', id: propertyId }]
})
})
})
export const {
// ...
useModifyContractMutation
} = contractApi
Store setup:
export const STORE_RESET_ACTION_TYPE = 'RESET_STORE'
const combinedReducer = combineReducers({
[photoApi.reducerPath]: photoApi.reducer,
[authApi.reducerPath]: authApi.reducer,
[propertyApi.reducerPath]: propertyApi.reducer,
[cronApi.reducerPath]: cronApi.reducer,
[contractApi.reducerPath]: contractApi.reducer,
auth: authReducer
})
const rootReducer: Reducer = (state: RootState, action: AnyAction) => {
if (action.type === STORE_RESET_ACTION_TYPE) {
state = {} as RootState
}
return combinedReducer(state, action)
}
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware().concat([
photoApi.middleware,
authApi.middleware,
propertyApi.middleware,
cronApi.middleware,
contractApi.middleware,
errorHandlerMiddleware
])
}
})
setupListeners(store.dispatch)
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
If those api services have data depending on each other (which is kinda implied by the fact that they should invalidate each other) they should not be multiple api services - they are really just multiple endpoints of one api. We state so at multiple places in the documentation.
Quoting the quickstart tutorial for example:
Typically, you should only have one API slice per base URL that your application needs to communicate with. For example, if your site fetches data from both /api/posts and /api/users, you would have a single API slice with /api/ as the base URL, and separate endpoint definitions for posts and users. This allows you to effectively take advantage of automated re-fetching by defining tag relationships across endpoints.
Instead, if you want to split that one api into multiple files, you can do so - using the code splitting mechanisms described in the documentation.
That also means that you do not have to add a lot of api slices and middlewares in your configureStore
call, but just one.