Customizing the return type of generated hooks
kallebornemark opened this issue · comments
My backend is passing an etag
header when I fetch data. I can access this in <namspace>Fetcher.ts
with response.headers.get("etag")
, and if I include this in the return object, the fetcher's return type is correctly inferred in <namspace>Components.ts
.
How do I access this in the generated React Query hook consuming this fetcher? From what I can tell, these hooks always return the same data as the endpoint in the Swagger spec, and I haven't found a way of configuring this.
A solution would of course be to use the fetcher directly, but that kind of defeats the point of generating the hook.
Hi 👋
Indeed, after playing with your usecase locally, this is not as trivial as thought 😕
A solution (a bit hacky) will be to add the definition of this etag
part of every response in the openapi specs, and inject it later on.
diff --git a/openapi-codegen.config.ts b/openapi-codegen.config.ts
index 3035058..dfe1c91 100644
--- a/openapi-codegen.config.ts
+++ b/openapi-codegen.config.ts
@@ -11,6 +11,17 @@ export default defineConfig({
},
outputDir: "src/petstore",
to: async (context) => {
+ Object.entries(context.openAPIDocument.components!.schemas!).forEach(
+ ([_key, value]) => {
+ if ("$ref" in value) return;
+
+ value.properties!.$etag = {
+ type: "string",
+ description: "etag header",
+ };
+ }
+ );
+
const filenamePrefix = "petstore";
const { schemasFiles } = await generateSchemaTypes(context, {
filenamePrefix,
So when I’m generating, I now have this type in all my components:
Let’s not forget to inject the value in our Fetcher:
--- a/src/petstore/petstoreFetcher.ts
+++ b/src/petstore/petstoreFetcher.ts
@@ -89,7 +89,10 @@ export async function petstoreFetch<
}
if (response.headers.get("content-type")?.includes("json")) {
- return await response.json();
+ return {
+ ...(await response.json()),
+ $etag: response.headers.get("etag"),
+ };
} else {
// if it is not a json response, assume it is a blob and cast it to TData
return (await response.blob()) as unknown as TData;
Please note that this is very hacky, this is for example not working for responses that return an array in my dummy example.
I will leave this issue open, we can definitely do better than this! I hope this is helping a bit
I appreciate you looking into this! 🙏 What you suggested is similar to the hack I attempted myself.
I've currently changed approach slightly by saving the ETag to localStorage at the fetcher level, removing the need to read this value at the hook consumer level.
So I'd consider my original issue resolved, but I understand if you want to keep it open for future development purposes.
Good to know that your issue is solved! I’m wondering why localStorage, it seems more than a global value (but I’m missing a lot of contexts ^^)
Just in case, you can just do window.etag = response.headers.get("etag")
and type the window
object like this:
declare global {
interface Window {
/**
* Value from `etag` header
*/
etag?: string
}
}
It just seemed reasonable for my use-case as I'm already storing some fetch-related things there. Do you see any benefits of using window
here?
Some benefits I can see:
- You can type it
- This is less prone to user modifications
But nothing critical 😉