Search code examples
typescriptredux-toolkit

How does @reduxjs/toolkit generate its APIs with dynamic hooks that are strongly-typed?


import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const output = createApi( {
    reducerPath: 'my-api',
    keepUnusedDataFor: CACHE_TIMEOUT,
    baseQuery: fetchBaseQuery( {
        baseUrl,
        fetchFn,
        prepareHeaders,
    } ),
    endpoints: ( builder ) => ( {
        foo: builder.query<any, Params>( {
            query: ( params: Params ) => {
                return {...};
            },
        } ),
        edit: builder.mutation<any, Params>( {
            query: ( params: Params ) => {
                return {...};
            }
        } ),
    } ),
} );

Here is a simple example of creating an API with @reduxjs/toolkit. In the above example, the output variable will have - among other methods & props - 3 hooks that are strongly-typed and generated off of the endpoints prop that are named as such:

  • output.useFooQuery

  • output.useLazyFooQuery

  • output.useEditMutation

I do not understand what is going on under the hood to generate the type that comes out the other side. Can anyone explain what is going on here (and possibly demonstrate it)?

I've tried following the source code a couple times without much luck. I haven't given up on that entirely yet, but at this point, I'm hoping someone just knows how it works to help save some time because I'd really like to use this technique at some point in the future. Thanks for any help you may provide


Solution

  • It uses mapped types with key remapping - you can find the documentation and some examples here in the official TypeScript documentation.

    Taking one example from there:

    type Getters<Type> = {
        [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
    };
     
    interface Person {
        name: string;
        age: number;
        location: string;
    }
     
    type LazyPerson = Getters<Person>;
    

    will result in this structure for LazyPerson:

    type LazyPerson = {
        getName: () => string;
        getAge: () => number;
        getLocation: () => string;
    }