Indicate HN: Producing Form-True React Hooks from OpenAPI

The Xata engineering team values predictability at all levels of our codebase. This blog post is about how we handle API communications in a predictable way. Motivation To us at Xata engineering, we consider spending time reverse engineering APIs just to know what kind of data is available to be neither fun nor valuable. Instead,…

117
Indicate HN: Producing Form-True React Hooks from OpenAPI

Meet this apt ingredient!

The Xata engineering crew values predictability in any respect levels of our codebase. This blog submit is ready how we tackle API communications in a predictable map.

Motivation

To us at Xata engineering, we sustain in mind spending time reverse engineering APIs lawful to understand what roughly records is on hand to be neither stress-free nor treasured. As an alternative, via OpenAPI we procure some insights into the potentialities uncovered by some APIs. Then as soon as more, why quit there? Studying an OpenAPI spec and manually writing code is… shall we embrace, also no longer our thing.

Obviously, some libraries exist to generate kinds from OpenAPI, however then we would restful must manually join them to react-ask or identical fetchers, all whereas the generated kinds don’t appear to be very thorough in most conditions. Even worse, in case of spherical dependencies in a given OpenAPI spec, this turns into much less of an risk for us.

Given these constraints, we saw likely for innovation. We needed to produce one thing! And because of this we began to craft openapi-codegen.

The dreams were sure:

  1. Generate human-readable kinds, so we are in a position to overview TypesScript files in desire to a mountainous OpenAPI YAML file.
  2. Embed all that that that you may take into accounts documentation, so we are in a position to private entry to the documentation straight via IntelliSense.
  3. Generate ready-to-recount React Hooks, with url and kinds baked into the factor to carry far off from any human errors.
  4. Be as flexible as that that that you may take into accounts: so far we are in a position to generate fetchers & react-ask hooks, however let’s no longer restrict the potentialities.

Let’s Play

To cherish about a of the superpowers this generator affords you, let’s develop a React application that consumes the Github API.

Initialize the Mission

For simplicity, let’s recount vitejs to bootstrap our mission!

$ npm develop vite@most modern

We are going to settle the framework react with the variant react-ts, this ought to restful give us a brief working ambiance for our application.

Now, let’s delivery the magic!

$ npm i -D @openapi-codegen/{cli,typescript}
$ npx openapi-codegen init
  • Opt Url
  • Paste the giphy OpenAPI url: https://api.apis.guru/v2/specs/github.com/1.1.4/openapi.yaml
  • Enter github as namespace
  • Opt react-ask substances
  • Enter src/github as folder

Develop npx openapi-codegen gen github and voilà!

Search for What We Bear

Let’s settle a shrimp little bit of time to ogle what we private got in ./src/github. The extra involving file is GithubComponents.ts, this is the set aside all our react-ask substances and fetchers are generated and likewise our entry point. We private got some generated kinds in Github{Parameters,Schemas,Responses}. These are a 1-1 mapping with what we private got within the OpenAPI spec.

To supply, we private got two particular files: GithubFetcher.ts and GithubContext.ts. These files are generated easiest as soon as and could simply also be modified/prolonged to pass smartly with your needs.

In GithubFetcher.ts that that you may:

  • inject the baseUrl (a default is equipped)
  • take care of authentication
  • tweak how ask strings are handled, especially in case that that you may need arrays

and in GithubContext.ts that that you may:

  • tweak how react-ask cache keys are handled (queryKeyFn)
  • inject runtime values (hooks) to the fetcher (fetcherOptions)
  • disable ask fetching (queryOptions)

Setup react-ask

Obviously, so far, we don’t even private react-ask installed, let’s produce this and setup our application.

$ npm i react-ask

Add the queryClient

// App.tsx
import { QueryClient, QueryClientProvider } from "react-ask";

const queryClient=new QueryClient();

feature App() {
  return (
    
      
    
  );
}

feature Customers() {
  return 
todo
; } export default App;

We are in a position to bustle yarn dev and gaze a white page with “todo”.

Open up Fetching!

Let’s try to procure some users!

import { QueryClient, QueryClientProvider } from "react-ask";
import { useSearchUsers } from "./github/githubComponents";

const queryClient=new QueryClient();

feature App() {
  return (
    
      
    
  );
}

feature Customers() {
  const { records, error, isLoading }=useSearchUsers({
    queryParams: { q: "fabien" },
  });

  if (error) {
    return (
      

{error.message}

Documentation
); } if (isLoading) { return
Loading…
; } return (
    {records?.objects.map((merchandise)=> (
  • {merchandise.login}
  • ))}
); } export default App;

And voilà! With out colorful the API, lawful taking half in with the autocompletion, I’m in a position to procure a listing a users! 🥳 The kinds even give us a hint that error.documentation_url turn out to be a thing, rather cool, however let’s gaze if this is de facto working!

Error Management

Let’s refresh the page typically, unless we reach the API price restrict 😅 In the raze, we don’t gaze the error message, nor the documentation link.

Here is the set aside GithubFetcher.ts has to be tweaked! Errors are certainly a shrimp bit trickier, as an error can even be factual object again from the API or the relaxation (text response in desire to JSON, or one thing much less predictable if the network is down), so we private got to develop determined we bought a identified structure in our application.

// GithubFetcher.ts
@@ -1,4 +1,5 @@
+import { BasicError } from "./githubSchemas";

@@ -43,8 +43,18 @@ export async feature githubFetch

Here we need to make sure to validate the error format, since everything is optional in BasicError GitHub API and BasicError have a message: string property, I can safely throw an Error. And just like this, we have nice and type-safe error handling.

Adding Some States

Of course, React is all about state isn’t it? So… let’s try!

function Users() {
  const [query, setQuery]=useState("");
  const { data, error, isLoading }=useSearchUsers(
    {
      queryParams: { q: query },
    },
    {
      enabled: Boolean(query),
    }
  );

  if (error) {
    return (
      

{error.message}

Documentation
); } return (
setQuery(e.target.value)} /> {isLoading ? (
Loading…
) : (
    {records?.objects.map((merchandise)=> (
  • {merchandise.login}
  • ))}
)}
); }

Now I’m in a position to peep for users, and likewise reach the API price restrict map faster! 😅 I verbalize this would possibly be a lawful time to introduce authentication handling in our shrimp application.

Authentication

To withhold it easy for our demo functions, let’s settle a GitHub developer token and store it in localStorage. Here is unsafe and likewise you most certainly mustn’t produce this in production. If the token is no longer space, ask for it, if it is there, present the records and provide a disconnect button. We can must entry this token at runtime later, with this in mind, let’s develop a Auth.tsx file, that isolates our authentication common sense.

// Auth.tsx
import React, { createContext, useContext, useState } from "react";
import { useQueryClient } from "react-ask";

const authContext=createContext({
  token: null,
});

export feature AuthProvider({ younger of us }: { younger of us: React.ReactNode }) {
  const key="githubToken";
  const [token, setToken]=useState(localStorage.getItem(key));
  const [draftToken, setDraftToken]=useState("");
  const queryClient=useQueryClient();

  return (
    
      {token ? (
        
          {younger of us}
          
        >
      ) : (
        
{ e.preventDefault(); setToken(draftToken); localStorage.setItem(key, draftToken); }} >

Please enter a non-public entry token

setDraftToken(e.target.impress)} >
)}
); } export const useToken=()=> { const { token }=useContext(authContext); return token; };

Now, we are in a position to wrap our App with the AuthProvider, so the token is on hand on your complete application:

// App.tsx
feature App() {
  return (
    
      
        
      
    
  );
}

and inject the token in GithubContext.ts

--- a/src/github/githubContext.ts
+++ b/src/github/githubContext.ts
@@ -1,4 +1,6 @@
 import kind { QueryKey, UseQueryOptions } from "react-ask";
+import { useToken } from "../useAuth";
 import { QueryOperation } from "./githubComponents";

 export kind GithubContext={
@@ -6,7 +8,9 @@ export kind GithubContext={
     /Headers to inject within the fetcher
      */
-    headers?: {};
+    headers?: {
+      authorization?: string;
+    };
     /Ask params to inject within the fetcher
      */
@@ -36,14 +40,22 @@ export feature useGithubContext(
-  _queryOptions?: Leave out,
     "queryKey" | "queryFn"
  >
 ): GithubContext {
+  const token=useToken();
+
   return {
-    fetcherOptions: {},
-    queryOptions: {},
+    fetcherOptions: {
+      headers: {
+        authorization: token ? `Bearer ${token}` : undefined,
+      },
+    },
+    queryOptions: {
+      enabled: token !==null && (queryOptions?.enabled ?? factual),
+    },
     queryKeyFn: (operation)=> {
       const queryKey: unknown[]=hasPathParams(operation)
         ? operation.course

And that’s it! Now we private got all the pieces in spot to develop an unheard of application spherical github API.

How is that this working? Let’s private a glance of the generated useSearchUsers to realise.

export const useSearchUsers=(
  variables: SearchUsersVariables,
  alternatives?: Leave out,
    "queryKey" | "queryFn"
 >
)=> {
  // 1. we retrieve our personalized auth common sense
  const { fetcherOptions, queryOptions, queryKeyFn }=
    useGithubContext(alternatives);
  return reactQuery.useQuery(
    queryKeyFn({
      course: "/search/users",
      operationId: "searchUsers",
      variables,
    }),
    // 2. the personalised `headers.authorization`, half of `fetcherOptions` is passed to the fetcher
    ()=> fetchSearchUsers({ ...fetcherOptions, ...variables }),
    {
      ...alternatives,
      ...queryOptions,
    }
  );
};

What About Ask Caching?

One in all the important thing facets of react-ask, is to private a ask cache. By default, openapi-codegen will give you a key cache that suits the URL scheme.

To illustrate:

useSearchUsers( { queryParams: { q: "fabien" } } will invent the next cache key: ["search", "users", { q: "fabien" }].

So if I desire to invalidate the users list, I’m in a position to call:

import { useQueryClient } from "react-ask";

const MyComp=()=> {
  const queryClient=useQueryClient();

  const onAddUser=()=> {
    queryClient.invalidateQueries(["search", "users"]);
  };

  /... */
};

Indicate: Here is lawful a default habits, in case that that you may need extra explicit needs, that that you may consistently tweak GithubContext#queryKeyFn.

Reduction & Feedback

This mission is restful somewhat early stage (however it strictly follows semver, so if we shatter the API, that that you may realize it!). We’re consistently delighted to private feedback and let you in case that that you may need any questions. Whenever you happen to would maintain to browse the code or settle it for a wander, develop particular to look at out the repo on GitHub.

Overjoyed coding of us!

>

>
+
TQueryFnData,>
>>>{>
code>
Read More
Fragment this on knowasiak.com to talk to of us on this topicRegister on Knowasiak.com now in case that that you may properly be no longer registered but.

Charlie Layers
WRITTEN BY

Charlie Layers

Fill your life with experiences so you always have a great story to tellBio: About: