So i have this component definition that accepts two generics
function AsyncFlatList<ApiResponse, Item>({}: {
url: string;
transformResponse: (response: ApiResponse) => Item[];
keyExtractor: (item: Item) => string;
}) {
// ignore the implementation
return <></>;
}
I call the component like this
type Post = {
id: number
title: string
body: string
userId: number
}
type PostMapped = {
id: string
title: string
body: string
}
<AsyncFlatList<Post[], PostMapped>
url="https://jsonplaceholder.typicode.com/posts"
transformResponse={(response) => response}
// The type of post params below is coming from `PostMapped`
keyExtractor={(post) => post.id}
/>
The problem is that I don't want to define PostMapped
explicitly. I want it to be inferred from the type of the return value on the transformResponse
prop.
So that i can use the component like this:
<AsyncFlatList<Post[]>
url="https://jsonplaceholder.typicode.com/posts"
transformResponse={(response) =>
response.map((singleResponse) => ({
id: singleResponse.id.toString(),
title: singleResponse.title,
body: singleResponse.body,
}))
}
// post types below will be inferred based on what's return from transformResponse
// that is why we can use post.id directly without types issue because it is already transformed to string
// in the transformResponse prop above
keyExtractor={(post) => post.id}
/>
and I don't have to define PostMapped
anymore since it will be inferred.
Kindly help to achieve that. Thanks
To make the minimal changes to your code, here is an example.
import React from 'react';
function AsyncFlatList<ApiResponse, Item = unknown>({}: {
url: string;
transformResponse: (response: ApiResponse) => Item[];
keyExtractor: (item: Item) => string;
}) {
// ignore the implementation
return <></>;
}
type Post = {
id: number
title: string
body: string
userId: number
}
const MyList: React.FC = () => {
return (
<AsyncFlatList
url="https://jsonplaceholder.typicode.com/posts"
transformResponse={(response: Post[]) =>
response.map((singleResponse) => ({
id: singleResponse.id.toString(),
title: singleResponse.title,
body: singleResponse.body,
}))}
keyExtractor={(post) => post.id}
/>);
}
In fact, the ApiResponse generic argument is not needed in your case (but I don't know if you'll need it later in your implementation).
Edit:
After reading your comment I think I better understand what result you want to really achieve: You want to define the contract of AsyncFlatList by passing the generic argument and inferring from the type of transformResponse
at the same time.
And It is not possible. You either explicitly pass the type arguments or let type argument inference do the job.
Let's look at a small non-react example:
//@ts-ignore
function testing<A,B>(cb1: (a:A)=>B, cb2: (b:B)=>string);
testing((a: Post)=>a, post=>post.body); // pass typecheck
testing<Post>(a=>a, post=>post.body); // fail typecheck, beacuse B is missing
//@ts-ignore
function testing<A=unknown,B=unknown>(cb1: (a:A)=>B, cb2: (b:B)=>string);
testing((a: Post)=>a, post=>post.body); // pass typecheck
testing<Post>(a=>a, post=>post.body); // fail typecheck, beacuse B is unknown by default