I'm trying to factorize some of my RTK Query code. Most of my endpoints are simple CRUD endpoints for entities, so a lot of my .injectEndpoints()
calls end up being exactly the same thing. I was thinking I could factorize it with some helper function like this one:
import { EndpointBuilder } from "@reduxjs/toolkit/dist/query/endpointDefinitions";
export const makeCrudEndpoints = <T, E extends string>(
entity: E,
path: string,
build: EndpointBuilder<any, any, any>
) => {
return {
[`add${entity}`]: build.mutation<T, Partial<T>>({
query: (body) => ({ method: "POST", url: path, body }),
}),
[`list${entity}s`]: build.query<T[], {}>({
query: () => ({
method: "GET",
url: path,
}),
}),
[`get${entity}`]: build.query<T, number>({
query: (id) => ({
method: "GET",
url: `${path}/${id}`,
}),
}),
} as const;
};
and use it like this:
const wallsApi = api.injectEndpoints({
endpoints: (build) => makeCrudEndpoints<Wall, "walls">("Wall", "walls", build),
});
export const { useGetWallQuery } = wallsApi;
But Typescript cannot find useGetWallQuery
in wallsApi
:
TS2339: Property 'useGetWallQuery' does not exist on type 'Api , { readonly [x: string]: MutationDefinition | QueryDefinition...> | QueryDefinition...>; }, "api", never, unique symbol | unique symbol>'.
So yeah, it got really complicated really quick, and I'm not sure what's the right thing to do next. I think the issue might be that the return type of the makeCrudEndpoints
function is inferred as an index-type ({[p: string]: (EndpointDefinitionWithQuery<T extends Primitive ?.....
) so I loose the type information of the keys.
I've tried to solve it using something like this, but the return type is not different.
return {
[`add${entity}` as `add${E}`]: build.mutation<T, Partial<T>>({
query: (body) => ({ method: "POST", url: path, body }),
}),
...
I'm aware that this has more to do with typescript than rtk-query, but I'm not sure where to start looking. Any thoughts, pointers?
CodePudding user response:
We've had some users try this but not with great success. If you find a solution, please open a discussion over on Github so we get that in the docs.
Generally, one observation from seeing your code and already having had that discussion a bit with some users:
Doing it with three endpoints is probably more complicated than just one. Maybe start with only one endpoint, extract that into a function and then have three functions each injecting one endpoint - you can have a parent function calling these three individually later.
CodePudding user response:
I found this post that gives a trick to force Typescript to preserve the key types. The following seems to work at first glance:
import { EndpointBuilder } from "@reduxjs/toolkit/dist/query/endpointDefinitions";
const makeEndpoint = <K extends string, T>(name: K, value: T) => {
return { [name]: value } as Record<K, T>;
};
export const makeCrudEndpoints =
<N extends string, T>(entityName: N, restResourcePath: string) =>
(build: EndpointBuilder<any, any, any>) => {
return {
...makeEndpoint(
`add${entityName}`,
build.mutation<T, Partial<T>>({
query: (body) => ({ method: "POST", url: restResourcePath, body }),
invalidatesTags: [`${entityName}s`],
})
),
...makeEndpoint(
`list${entityName}s`,
build.query<T[], void>({
query: () => ({
method: "GET",
url: restResourcePath,
}),
providesTags: [`${entityName}s`],
})
),
...makeEndpoint(
`get${entityName}`,
build.query<T, number>({
query: (id) => ({
method: "GET",
url: `${restResourcePath}/${id}`,
}),
})
),
} as const;
};
const wallApi = api.injectEndpoints({
endpoints: makeCrudEndpoints<"Wall", Wall>("Wall", "walls"),
});
export const { useGetWallQuery, useListWallsQuery, useAddWallMutation } =
wallApi;
This needs further testing, a call to enhanceEndpoints()
might be needed to declare the tags.