Reputation: 1768
I'd like to know if it's somehow possible, in TypeScript, to infer a type based on the prop of the parent.
Let me explain better with an example: suppose I have <Parent>
and <Child>
components, and an interface IMyComplexObject
.
interface IMyComplexObject {
name: string;
surname: string;
}
const MyApp = (props) => {
const [obj, setObj] = React.useState<IMyComplexObject>({name: 'Foo', surname: 'Bar'});
return (
<Parent context={obj}>
<Child value={'name'} />
</Parent>
);
}
So, what I'm asking is: when I write <Child value={'name'} />
, is it possible to infer the accepted values for that value
prop to be 'name' | 'surname'
, which are the keys of obj
?
I know that, if I had only one component, things would be easy. Suppose this:
interface IChildProps<T> {
ctx: T;
value: keyof T;
}
- - -
return <Child ctx={obj} value={'name'} />
Then, the the intellisense would telling me which are the accepted values:
But, things get trickier when it's the <Parent>
component that defines the context, and not the component itself.
EDIT:
To answer @captain-yossarian proposal, the issue with generic type on <Child>
is that I would end up with a code like:
<Parent context={obj}>
<Child<IMyComplexObject> value={...} />
<Child<IMyComplexObject> value={...} />
<Child<IMyComplexObject> value={...} />
...
</Parent>
Way too many generics (one for each child). More important, there's NO actual connection between the type of the obj
passed to <Parent>
and the generic types passed to the <Child>
s component.
So, for instance, something like this would still be 'legal':
interface IObj_1 {
age: number;
}
interface IObj_2 {
name: string;
}
const [obj1, setObj1] = React.useState<IObj_1>({age: 7});
const [obj2, setObj2] = React.useState<IObj_2>({name: 'Foo'});
return (
<Parent context={obj1}>
<Child<IObj_2> value={'name'} />
</Parent>
);
Upvotes: 2
Views: 2857
Reputation: 33041
Yes , it is possible.
You need to provide an explicit generic argument to Child component.
<Child<MyComplexObject> value={"name"} >
and Child implementation:
const Child=<T,>(props: {value: keyof T})=>null
Or
const Child=<T,>(props: {ctx: T, value: keyof T})=>null
UPDATE
I think the most simple approach will be to provide Parent
with extra props: items
and callback
items
- array of keys of IMyComplexObject
. items
itself is a primitive value which goes straight to value
property of Child
callback
- Array.prototype.map
callback.
import React, { useState } from 'react'
interface IMyComplexObject {
name: string;
surname: string;
}
type ParentProps<Context> = {
context: Context;
items: Array<keyof Context>;
callback: (prop: keyof Context) => JSX.Element
}
type ChildProps = { value: string }
const Parent = <Context,>({ items, callback }: ParentProps<Context>) =>
<>
{items.map(callback)}
</>
const Child = ({ value }: ChildProps) => <p>{value}</p>
const MyApp = () => {
const [context, setContext] = useState<IMyComplexObject>({ name: 'Foo', surname: 'Bar' });
return (
<>
<Parent
context={context}
items={['name']}
callback={
(prop) => <Child value={prop} />
}
/>
<Parent
context={context}
items={['hello']} /** expected error */
callback={
(prop) => <Child value={prop} />
}
/>
</>
);
}
Upvotes: 1