Matching the Expected Type with Custom SWR Hook's Return Type

Currently, I am working on integrating swr into my project to create a custom block hook using generics. The goal is to have this hook creator accept mapParamsToKey and request methods as parameters and generate an SWR hook function. However, there seems to be an issue with the return type of my custom hook, SWRResponse, which is not matching the expected type. Below is a snippet of the code I have written for reference.

import useSWR from 'swr';

type Params = {
  id: number;
};
type BlogPost = {
  id: number;
  title: string;
};

const mapParamsToKey = (params: Params) => ['blog-post', params.id];
const request = (params: Params): Promise<BlogPost> => {
  const data = { id: params.id, title: `Blog Post ${params.id}` };
  return Promise.resolve(data);
};

const useBlogPost = (params: Params) =>
  useSWR(mapParamsToKey(params), () => request(params)); 
// (params: Params) => SWRResponse<BlogPost, any, any>


// swr hooks creator
function createBlockHook<P, T = any>(config: {
  mapParamsToKey: (params: P) => Array<string | number>;
  request: (params: P) => Promise<T>;
}) {
  return (params: P) =>
    useSWR(config.mapParamsToKey(params), () => config.request(params));
}

const useBlogPost2 = createBlockHook<Params>({
  mapParamsToKey,
  request,
}); 
// (params: Params) => SWRResponse<any, any, any>
// SWRResponse data type is any, but `BlogPost` is expected (infer from `request` param)

Here is how the hook should ideally be used:

import client from './my-api-client';
type CommentParams = { id: number; } 
const useComment = createBlockHook<CommentParams>({
  mapParamsToKey: (params) => ['comment', params.id], 
  request: client.getComment, // (params: CommentParams) => Promise<Comment>
}); 

function Comment(props: { id: number }) {
  const swr = useComment(props); 
  if (swr.data) {
    // swr.data with type hint `Comment`
    return (
      <div>{swr.data.content}</div>
    )
  }
  return <div>...</div>;
}

Answer №1

If you're looking to specify the input and output of useSWR with your API, we were able to achieve that using a code structure similar to this:

type ApiEndpoint = {
  'posts/{postId}/comments': {
    input: { postId: string },
    output: { comments: Comment[] }
  },
  // ...

}

export function useCustomApi<E extends keyof ApiEndpoint>(
  endpointKey: E | null | undefined | false,
  params?: Partial<ApiEndpoint[E]['input']>,
  options?: SWRConfiguration,
) {
  return useSWR<ApiEndpoint[E]['output'], HTTPError>(
    endpointKey && [endpointKey, params],
    fetcher,
    options,
  );
}

To define your API in ApiEndpoint and make use of it, simply call

useCustomApi('posts/{postId}/comments', {postId})
. Make sure to adjust your fetcher function accordingly for properly typed results.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Trigger an error in TypeScript with an embedded inner error

Is it possible to throw an Error with an inner Error in TypeScript, similar to how it's done in C#? In C#, you can achieve this by catching the exception and throwing a new one with the original exception as its inner exception: try { var a = 3; ...

Leverage advanced type deduction in Key Remapping

I'm puzzled by this code snippet: type Foo<T extends string> = [T] extends [infer Y] ? Y : never // works fine type Test_2<T extends Array<string>> = { [P in T[number] as Foo<"foo">]: undefined } // no issues type ...

Is OnPush Change Detection failing to detect state changes?

Curious about the issue with the OnPush change detection strategy not functioning properly in this demonstration. My understanding is that OnPush change detection should activate when a property reference changes. To ensure this, a new array must be set e ...

The number in Typescript should fall between 0 and 1, inclusive

Is there a method in Typescript that guarantees the value of a number will be less than or greater than a certain threshold? Currently, it permits the specification of a range of values, but I'm unsure about comparison. This is similar to what I have ...

Watching the Event Emitters emitted in Child Components?

How should we approach utilizing or observing parent @Output() event emitters in child components? For instance, in this demo, the child component utilizes the @Output onGreetingChange as shown below: <app-greeting [greeting]="onGreetingChange | a ...

Using Vue.js 2 on multiple HTML pages with Typescript and ASP.Net Core

My ASP.Net Core MVC project utilizes VueJs2 for more complex tasks, with each view having its own corresponding js file. The directory structure is as follows: ├ Controllers\HomeController.cs (with actions Index & Details) ├ Scripts\Hom ...

Verify the length of an array within an object using JavaScript

I am facing a problem with an object. Here is what it looks like: const array = { "entities": [ { "annexes": [ { "buildingUniqueIds": [] }, { ...

Tips for utilizing the latest hook feature in Typegoose

After adding a pre hook on updateOne events, I noticed it functions differently compared to save events... I believe this discrepancy is due to the fact that the update command typically includes a matcher as its first argument. I attempted to capture the ...

When using the Composition API in Vue 3, the "Exclude" TypeScript utility type may result in a prop validation error

Currently, I am utilizing Vue 3 alongside the Composition API and TypeScript, all updated to their latest stable versions. If we take a look at the types below: export interface Person { name: string; } export type Status = Person | 'UNLOADED&ap ...

Using TypeScript to destructure arrays within a parameter list

As I delve into TypeScript, my focus is on mastering array destructuring within the arguments list. While object destructuring is feasible using this method: let foo = function({firstname, lastname}){...} foo({ firstname: 'ralph', lastname ...

Executing a secondary API based on the data received from the initial API call

Currently, I am diving into the world of RxJS. In my project, I am dealing with 2 different APIs where I need to fetch data from the first API and then make a call to the second API based on that data. Originally, I implemented this logic using the subscri ...

Error in Typescript stating that the property 'children' is not found on the imported interface of type 'IntrinsicAttributes & Props'

When I try to import an interface into my Card component and extend CardProps, a yarn build (Typescript 4.5.4) displays the following error: Type error: Type '{ children: Element[]; className: string; border: true; disabled: boolean; }' is not as ...

Incorporating TypeScript with jQuery for efficient AJAX operations

I recently added jQuery typings to my TypeScript project. I am able to type $.ajax(...) without encountering any compile errors in VS Code. However, when I test it on localhost, I receive an error stating that "$ is not defined." In an attempt to address t ...

Oops! Looks like there's an issue with the type error: value.forEach is

I am working on creating an update form in Angular 6 using FormArray. Below is the code snippet I have in editfrom.TS : // Initialising FormArray valueIngrident = new FormArray([]); constructor(private brandService: BrandService, private PValueInfoSe ...

What is the best way to share type definitions between a frontend and a Golang backend application?

I utilized typescript for both the frontend (Angular) and backend (Express). To ensure type definitions are shared, I created a file called shared-type-file.ts. interface Kid{ name: string; age: number; } By then running npm install in both the front ...

How can the `!` operator be utilized in MikroORM Typescript entities?

How can I declare a key in a JS object with an ! before the colon? MikroORM syntax for class @Entity() export class Post { // Using @PrimaryKey() decorator to designate primary key @PrimaryKey() id!: number; @Property({ type: "date", de ...

Unsubscribing from a nested observable - a step-by-step

In our Angular component, we leverage the ngOnDestroy() method to handle canceling http requests that are still pending when navigating away from a page. To avoid reloading data that has already been fetched, we utilize a custom generic cache helper on cer ...

Incorporate Ng-Survey multiple times within one component

Incorporating the ng-surveys template into my Angular application via has been successful. However, I encountered an issue where when using the template selector *ngFor to display multiple surveys on the same page, the browser treats all the surveys as id ...

The variable 'React' is defined but not utilized in the code

Here's the code snippet in question: // tslint:disable import * as React from 'react'; import { Input, InputProps } from '../atoms/Input/Input'; import { FormControl } from '../hoc/FormControl/FormControl'; export const ...

The code inside the promise .then block is executing long before the promise has completed its

After spending quite some time working on this messy code, I finally have a functioning solution: loadAvailabilities() { let promises = []; let promises2 = []; let indexi = 0; //return new Promise((resolve, reject) => { this.appo ...