JSPM

  • Created
  • Published
  • Downloads 202827
  • Score
    100M100P100Q185638F
  • License MIT

OpenAPI React Query Codegen

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@7nohe/openapi-react-query-codegen) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    OpenAPI React Query Codegen

    Node.js library that generates React Query (also called TanStack Query) hooks based on an OpenAPI specification file.

    npm version

    Features

    • Generates custom react hooks that use React Query's useQuery, useSuspenseQuery, useMutation and useInfiniteQuery hooks
    • Generates query keys and functions for query caching
    • Generates pure TypeScript clients generated by @hey-api/openapi-ts

    Installation

    $ npm install -D @7nohe/openapi-react-query-codegen

    Register the command to the scripts property in your package.json file.

    {
      "scripts": {
        "codegen": "openapi-rq -i ./petstore.yaml -c axios"
      }
    }

    You can also run the command without installing it in your project using the npx command.

    $ npx --package @7nohe/openapi-react-query-codegen openapi-rq -i ./petstore.yaml -c axios

    Usage

    $ openapi-rq --help
    
    Usage: openapi-rq [options]
    
    Generate React Query code based on OpenAPI
    
    Options:
      -V, --version              output the version number
      -i, --input <value>        OpenAPI specification, can be a path, url or string content (required)
      -o, --output <value>       Output directory (default: "openapi")
      -c, --client <value>       HTTP client to generate (choices: "angular", "axios", "fetch", "node", "xhr", default: "fetch")
      --request <value>          Path to custom request file
      --format <value>           Process output folder with formatter? (choices: "biome", "prettier")
      --lint <value>             Process output folder with linter? (choices: "biome", "eslint")
      --operationId              Use operation ID to generate operation names?
      --serviceResponse <value>  Define shape of returned value from service calls (choices: "body", "response", default: "body")
      --base <value>             Manually set base in OpenAPI config instead of inferring from server value
      --enums <value>            Generate JavaScript objects from enum definitions? ['javascript', 'typescript', 'typescript+namespace']
      --enums <value>            Generate JavaScript objects from enum definitions? (choices: "javascript", "typescript")
      --useDateType              Use Date type instead of string for date types for models, this will not convert the data to a Date object
      --debug                    Run in debug mode?
      --noSchemas                Disable generating JSON schemas
      --schemaType <value>       Type of JSON schema [Default: 'json'] (choices: "form", "json")
      --pageParam <value>        Name of the query parameter used for pagination (default: "page")
      --nextPageParam <value>    Name of the response parameter used for next page (default: "nextPage")
      -h, --help                 display help for command

    Example Usage

    Command

    $ openapi-rq -i ./petstore.yaml

    Output directory structure

    - openapi
      - queries
        - index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
        - common.ts <- common types
        - queries.ts <- generated query hooks
        - suspenses.ts <- generated suspense hooks
        - prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
      - requests <- output code generated by @hey-api/openapi-ts

    In your app

    Using the generated hooks
    // App.tsx
    import { usePetServiceFindPetsByStatus } from "../openapi/queries";
    function App() {
      const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
    
      return (
        <div className="App">
          <h1>Pet List</h1>
          <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
        </div>
      );
    }
    
    export default App;
    Using the generated typescript client
    import { useQuery } from "@tanstack/react-query";
    import { PetService } from "../openapi/requests/services";
    import { usePetServiceFindPetsByStatusKey } from "../openapi/queries";
    
    function App() {
      // You can still use the auto-generated query key
      const { data } = useQuery({
        queryKey: [usePetServiceFindPetsByStatusKey],
        queryFn: () => {
          // Do something here
          return PetService.findPetsByStatus(["available"]);
        },
      });
    
      return <div className="App">{/* .... */}</div>;
    }
    
    export default App;
    Using Suspense Hooks
    // App.tsx
    import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
    function ChildComponent() {
      const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
    
      return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>;
    }
    
    function ParentComponent() {
      return (
        <>
          <Suspense fallback={<>loading...</>}>
            <ChildComponent />
          </Suspense>
        </>
      );
    }
    
    function App() {
      return (
        <div className="App">
          <h1>Pet List</h1>
          <ParentComponent />
        </div>
      );
    }
    
    export default App;
    Using Mutation hooks
    // App.tsx
    import { usePetServiceAddPet } from "../openapi/queries";
    
    function App() {
      const { mutate } = usePetServiceAddPet();
    
      const handleAddPet = () => {
        mutate({ name: "Fluffy", status: "available" });
      };
    
      return (
        <div className="App">
          <h1>Add Pet</h1>
          <button onClick={handleAddPet}>Add Pet</button>
        </div>
      );
    }
    
    export default App;
    Invalidating queries after mutation

    Invalidating queries after a mutation is important to ensure the cache is updated with the new data. This is done by calling the queryClient.invalidateQueries function with the query key used by the query hook.

    Learn more about invalidating queries here.

    To ensure the query key is created the same way as the query hook, you can use the query key function exported by the generated query hooks.

    import {
      usePetServiceFindPetsByStatus,
      usePetServiceAddPet,
      UsePetServiceFindPetsByStatusKeyFn,
    } from "../openapi/queries";
    
    // App.tsx
    function App() {
      const [status, setStatus] = React.useState(["available"]);
      const { data } = usePetServiceFindPetsByStatus({ status });
      const { mutate } = usePetServiceAddPet({
        onSuccess: () => {
          queryClient.invalidateQueries({
            // Call the query key function to get the query key
            // This is important to ensure the query key is created the same way as the query hook
            // This insures the cache is invalidated correctly and is typed correctly
            queryKey: [UsePetServiceFindPetsByStatusKeyFn({
              status
            })],
          });
        },
      });
    
      return (
        <div className="App">
          <h1>Pet List</h1>
          <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
          <button
            onClick={() => {
              mutate({ name: "Fluffy", status: "available" });
            }}
          >
            Add Pet
          </button>
        </div>
      );
    }
    
    export default App;
    Using Infinite Query hooks

    This feature will generate a function in infiniteQueries.ts when the name specified by the pageParam option exists in the query parameters and the name specified by the nextPageParam option exists in the response.

    Example Schema:

    paths:
      /paginated-pets:
        get:
          description: |
            Returns paginated pets from the system that the user has access to
          operationId: findPaginatedPets
          parameters:
            - name: page
              in: query
              description: page number
              required: false
              schema:
                type: integer
                format: int32
            - name: tags
              in: query
              description: tags to filter by
              required: false
              style: form
              schema:
                type: array
                items:
                  type: string
            - name: limit
              in: query
              description: maximum number of results to return
              required: false
              schema:
                type: integer
                format: int32
          responses:
            '200':
              description: pet response
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      pets:
                        type: array
                        items:
                          $ref: '#/components/schemas/Pet'
                      nextPage: 
                        type: integer
                        format: int32
                        minimum: 1

    Usage of Generated Hooks:

    import { useDefaultServiceFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries";
    
    const { data, fetchNextPage } = useDefaultServiceFindPaginatedPetsInfinite({
      limit: 10,
      tags: [],
    });
    Runtime Configuration

    You can modify the default values used by the generated service calls by modifying the OpenAPI configuration singleton object.

    It's default location is openapi/requests/core/OpenAPI.ts and it is also exported from openapi/index.ts

    Import the constant into your runtime and modify it before setting up the react app.

    /** main.tsx */
    import { OpenAPI as OpenAPIConfig } from './openapi/requests/core/OpenAPI';
    ...
    OpenAPIConfig.BASE = 'www.domain.com/api';
    OpenAPIConfig.HEADERS = {
      'x-header-1': 'value-1',
      'x-header-2': 'value-2',
    };
    ...
    ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
      <React.StrictMode>
        <QueryClientProvider client={queryClient}>
          <App />
        </QueryClientProvider>
      </React.StrictMode>
    );
    

    Development

    Install dependencies

    pnpm install

    Run tests

    pnpm test

    Run linter

    pnpm lint

    Run linter and fix

    pnpm lint:fix

    Update snapshots

    pnpm snapshot

    Build example and validate generated code

    npm run build && pnpm --filter @7nohe/react-app generate:api && pnpm --filter @7nohe/react-app test:generated 

    License

    MIT