Neo's Playing Morpheus

    react-query-swagger
    TypeScript icon, indicating that this package has built-in type declarations

    14.4.3 • Public • Published

    react-query-swagger

    npm version npm MIT Types - TypeScript

    This projects autogenerates @tanstack/query hooks based on Swagger API definitions.

    So, given that you have a petstore-like API definition, you could autogenerate a list of react-query hooks, to call GET methods from the API (queries). Mutations (POST/PUT/PATCH/DELETE methods) are not supported yet.

    How to add

    Install the package into your project using yarn/npm (as a dev-dependency). You'll also need to add react-query (which you probably already have if you are interested in this library).

    yarn add react-query-swagger
    

    Then create/update your autogenerated hooks by calling (adjusting the URL and output path)

    yarn react-query-swagger /input:https://petstore.swagger.io/v2/swagger.json /output:src/api/axios-client.ts /template:Axios
    

    If you upgraded to @tanstack/react-query please add a switch /tanstack to commands

    Note that this library requires dotnet runtime installed on your machine! If you have runtime different from .NET Core 6, please add a switch (e.g. /runtime:Net50) to all commands.

    This will generate API clients based on Axios. If you prefer fetch, just use it as a template (mind the last parameter)

    yarn react-query-swagger /input:https://petstore.swagger.io/v2/swagger.json /output:src/api/axios-client.ts /template:Fetch
    

    You will probably want to add this script to your package.json to call it every time your API changes.

    All parameters are passed to NSwag, you could read about them in NSwag documentation. Personally I tend to use it with few additional parameters, which are combined under /use-recommended-configuration:

    yarn react-query-swagger /tanstack /input:https://petstore.swagger.io/v2/swagger.json /output:src/api/axios-client.ts /template:Axios /serviceHost:. /use-recommended-configuration
    

    How to use

    You could check a pet-client example, which shows the list of pets. It's a standard react-query setup, to query some pet data you just need to write:

      const petsQuery = AxiosQuery.ClientQuery.useFindPetsByStatusQuery([
        Status.Available,
        Status.Pending,
        Status.Sold,
      ]);
    

    Configuration

    setBaseUrl(baseUrl: string)

    Sets base URL for all queries

    setAxiosFactory(factory: () => AxiosInstance)

    Sets the function which returns Axios instance to be used in http request. By default axios.create() is called for every http request (this method only exists if you generated client using Axios template).

    setFetchFactory(factory: () => { fetch(url: RequestInfo, init?: RequestInit): Promise })

    Sets the function to return the fetchfunction to be used in http request. By default window is returned, which contains the default fetch function. This method only exists if you used Fetch template.

    Configure query options

    You could define additional UseQueryOptions for each query by setting *queryName*DefaultOptions

    AxiosQuery.ClientQuery.findPetsByStatusDefaultOptions = {
      cacheTime: 10000
    }
    

    persistQueryClient support

    React-query has an experimental support for persisting and restoring query cache (to preserve the cache between e.g. browser restarts). react-query-swagger requires additional configuration to correctly work with hydration (cache restoration) because of:

    1. All internal DTOs are JS classes, which are not recreated by JSON.parse (which is used by persistors by default).
    2. react-query-swagger has Date objects in DTOs, which are not restored by JSON.parse as well.

    So to make them work together correctly, you have to provide a special hydration function (which is autogenerated along with API clients):

     const localStoragePersistor = createWebStoragePersistor({
      storage: window.localStorage,
      
      // You need to import `persistorDeserialize` function from your `api-client.ts` and specify it as a deserialize function. 
      deserialize: persistorDeserialize,
     })

    QueryMetaProvider

    Injects meta option to all queries in children components. Might be useful if e.g. you want to refetch all queries in certain part of your app.

    First wrap your component in QueryMetaProvider and specify your meta tags (make sure they are constant):

    <QueryMetaProvider meta={headerMeta}>
        { /* Your app components (e.g. AppHeader */ }
    </QueryMetaProvider>
    
    const headerMeta = { region: 'header' }
    

    You could refetch based on meta via the following call:

    queryClient.refetchQueries({ predicate: (query) => ((query as any).observers as QueryObserver[]).find((observer) => observer.options.meta?.region === 'header') })
    

    Additional flags

    In addition to NSwag parameters we have 2 specific parameters:

    /fix-null-undefined-serialization

    This flag executes few regex replaces over the generated code. This is an easy way to achieve the behavior we want without forking and maintaining NSwag & NJsonSchema templates ourselves.

    Here are the regex rules and rationale behind them:

    • | undefined; is replaced by | null;

      Replaces DTO type definitions:

      export interface IUser {
         id?: number | undefined;   ->  id?: number | null;
      }
      

      Replace is made because this is what server (at least .NET :)) actually returns (at least by default)

    • : undefined is replaced by : null

      Changes init() function from:

      this.lastChangeDateTime = _data["lastChangeDateTime"] ? new Date(_data["lastChangeDateTime"].toString()) : <any>undefined;
      

      to

      this.lastChangeDateTime = _data["lastChangeDateTime"] ? new Date(_data["lastChangeDateTime"].toString()) : <any>null;
      

      Again, server actually returns null, we don't want to change that.

    • ? this.(...).toISOString() : null is replaced by && this.$1.toISOString()

      Performs the following change (in toJSON() method), from:

      data["shipDate"] = this.shipDate ? this.shipDate.toISOString() : <any>null;
      

      to

      data["shipDate"] = this.shipDate && this.shipDate.toISOString();
      

      This is to be able to send both undefined and null to the server (important for PATCH requests)

    • ? formatDate(...) : null is replaced by && formatDate(...)

      Performs the following change (in toJSON() method), from:

      data["shipDate"] = this.shipDate ? formatDate(this.shipDate) : <any>null;
      

      to

      data["shipDate"] = this.shipDate && formatDate(this.shipDate);
      

      This is to be able to send both undefined and null to the server (important for PATCH requests)

    /use-recommended-configuration

    This option basically passes the following parameters to NSwag /fix-null-undefined-serialization /generateOptionalParameters:true /typeStyle:Class /markOptionalProperties:true /nullValue:undefined /generateConstructorInterface:true.

    Here's a rationale behind each of them:

    • /generateOptionalParameters:true

      Otherwise, optional parameters are generated as mandatory. E.g.:

      • true: deletePet(petId: number, api_key?: string | null | undefined)
      • false: deletePet(petId: number, api_key: string | null | undefined) `
    • /typeStyle:Class

      Otherwise, if typeStyle is Inteface, there's no code to convert Date objects

    • /markOptionalProperties:true

      Otherwise PATCH dtos have all their properties defined as mandatory:

      export interface PatchUserDto {
          userName!: string | null; 
          // should be: userName?: string | null;
      }
      
    • /nullValue:undefined

      If we use null as null value, unnecessary code gets added to .toJSON() and .init() functions:

      toJSON(data?: any) {  
          data = typeof data === 'object' ? data : {};
        
          // nullValue:undefined 
          data["enabled"] = this.enabled;
        
          // nullValue:null
          data["enabled"] = this.enabled !== undefined ? this.enabled : <any>null; 
      }
      
      init(_data?: any) {  
         if (_data) {
             // nullValue:undefined
             this.enabled = _data["enabled"];
            
             // nullValue:null
             this.enabled = _data["enabled"] !== undefined ? _data["enabled"] : <any>null;
         }
      }
      
    • /generateConstructorInterface:true

      This gives a typed-possibility to create classes from interfaces (otherwise you have to use init(_data?: any) method)

    • /fix-null-undefined-serialization

      We need this to be able to use both undefined and null as values in PATCH requests

    How does it work

    Under the cover it's just a couple of template files for NSwag and a small script to easily use them.

    Contributions and support

    Issues and Pull Requests are welcome.

    For any kind of private consulting or support you could contact Artur Drobinskiy directly via email.

    Install

    npm i react-query-swagger

    DownloadsWeekly Downloads

    633

    Version

    14.4.3

    License

    MIT

    Unpacked Size

    135 kB

    Total Files

    57

    Last publish

    Collaborators

    • shaddix