Home > Enterprise >  How to inject endpoints in a type safe way in RTK Query?
How to inject endpoints in a type safe way in RTK Query?

Time:09-07

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.

  • Related