Advanced typings
Lets assume you're trying to create a generic component that receives a data-fetching function, lets call it fn, which returns an item or an array of some type and another prop, lets call it key, with one of the keys of the function's return type. In order to get autocompletion for the key prop. The data-fetching function async function getData() { return {key1: "b", key2: 4, key3: false}; } The Question How to declare the type of another component's prop to accept only the keys of the data type returned by the data-fetching function. The Process You could "easily" type the props as follows: type ReturnedType = { key1: string key2: number key3: boolean } type ComponentProps = { fn: () => Promise key: "key1" | "key2" | "key3" } But this doesn't really scale as the component is not generic, meaning the data-fetching function can only be one that returns an object with the shape of ReturnedType. Also, every time the type ReturnedType changes, we need to make changes to the type of the key prop. Note We could also declare the key's prop type as keyof ReturnedType. The Fix: keyof Instead of defining the props type, we will define our component's type and use keyof to define the type of key prop, resulting in the type being the union of keys of the awaited response of the function we will be passing on fn. TL;DR: type ComponentType = Promise, D extends Awaited > ({ fn, key }: { fn: F, key: keyof D }) => React.JSX.Element | null In case the function returns an array: type ComponentType = Promise, D extends Awaited[number] > ({ fn, key }: { fn: F, key: keyof D }) => React.JSX.Element | null In this case we need to check for the type of key prop to be "string": // Server component const Component: ComponentType = async ({fn, key}) => { const item = await fn(); if (typeof key !== "string") return null; return {item[key]} } // Client component const Component: ComponentType = ({fn, key}) => { const [item, setItem] = useState(); useEffect(() => { fn().then(setItem) }, []) if (typeof key !== "string") return null; return {item[key]} } Another way to do it (with a function returning an array and using a inferring helper): type KeyOf = T extends Record ? K : never; async function Component Promise>(fn: F, key: KeyOf): React.JSX.Element { const item = await fn() // there is no need to check for key to be string return {item[key]}; }

Lets assume you're trying to create a generic component that receives a data-fetching function, lets call it fn
, which returns an item or an array of some type and another prop, lets call it key
, with one of the keys of the function's return type.
In order to get autocompletion for the key
prop.
The data-fetching function
async function getData() {
return {key1: "b", key2: 4, key3: false};
}
The Question
How to declare the type of another component's prop to accept only the keys of the data type returned by the data-fetching function.
The Process
You could "easily" type the props as follows:
type ReturnedType = {
key1: string
key2: number
key3: boolean
}
type ComponentProps = {
fn: () => Promise<ReturnedType>
key: "key1" | "key2" | "key3"
}
But this doesn't really scale as the component is not generic, meaning the data-fetching function can only be one that returns an object with the shape of ReturnedType.
Also, every time the type ReturnedType changes, we need to make changes to the type of the key
prop.
Note
We could also declare thekey
's prop type askeyof ReturnedType
.
The Fix: keyof
Instead of defining the props type, we will define our component's type and use keyof
to define the type of key
prop, resulting in the type being the union of keys of the awaited response of the function we will be passing on fn
.
TL;DR:
type ComponentType = <
F extends () => Promise<Record<string, unknown>>,
D extends Awaited<ReturnType<F>>
> ({
fn,
key
}: {
fn: F,
key: keyof D
}) => React.JSX.Element | null
In case the function returns an array:
type ComponentType = < F extends () => Promise<Record<string, unknown>>, D extends Awaited<ReturnType<F>>[number] > ({ fn, key }: { fn: F, key: keyof D }) => React.JSX.Element | null
In this case we need to check for the type of key
prop to be "string":
// Server component
const Component: ComponentType = async ({fn, key}) => {
const item = await fn();
if (typeof key !== "string") return null;
return <>{item[key]}</>
}
// Client component
const Component: ComponentType = ({fn, key}) => {
const [item, setItem] = useState();
useEffect(() => {
fn().then(setItem)
}, [])
if (typeof key !== "string") return null;
return <>{item[key]}</>
}
Another way to do it (with a function returning an array and using a inferring helper):
type KeyOf<T> = T extends Record<infer K extends string, unknown> ? K : never;
async function Component<F extends () => Promise<Record<string, unknown>[]>>(fn: F, key: KeyOf<Awaited<ReturnType<F>>[number]>): React.JSX.Element {
const item = await fn()
// there is no need to check for key to be string
return <>{item[key]}</>;
}