In React I have a component that has render props that can be named dynamically
interface ComponentProps {
renderPropNames: string[];
renderProps: Record<string, () => JSX.Element>;
}
The main issue is that I want to use the component that is using this interface
<Component
renderPropNames:["prop1", "prop2", "prop3"
renderProps:{{
prop1: () => <div>Prop 1</div>
prop2: () => <div>Prop 2</div>
prop3: () => <div>Prop 3</div>
}}
/>
the name of the renderProps can be checked by TypeScript based on the renderPropNames
is there a way to do this dynamically in one type/inteface?
I can of course make a type like union of strings
type myStaticPropNames = "prop1" | "prop2" "prop3"
and this will be type checked and will work, but I cannot make the dynamically added strings outside this to be checked
In React I have a component that has render props that can be named dynamically
interface ComponentProps {
renderPropNames: string[];
renderProps: Record<string, () => JSX.Element>;
}
The main issue is that I want to use the component that is using this interface
<Component
renderPropNames:["prop1", "prop2", "prop3"
renderProps:{{
prop1: () => <div>Prop 1</div>
prop2: () => <div>Prop 2</div>
prop3: () => <div>Prop 3</div>
}}
/>
the name of the renderProps can be checked by TypeScript based on the renderPropNames
is there a way to do this dynamically in one type/inteface?
I can of course make a type like union of strings
type myStaticPropNames = "prop1" | "prop2" "prop3"
and this will be type checked and will work, but I cannot make the dynamically added strings outside this to be checked
Share Improve this question edited Mar 13 at 10:29 jonrsharpe 122k30 gold badges268 silver badges476 bronze badges asked Mar 11 at 5:52 michal skulamichal skula 112 bronze badges2 Answers
Reset to default 0Ensuring that the keys in your renderProps
object match the entries in your renderPropNames
array can be effectively achieved by utilizing TypeScript's type system.
Define the renderPropNames
Array with as const
:
hence, you create a tuple of string literals
const renderPropNames = ['prop1', 'prop2', 'prop3'] as const;
Derive a Union Type from renderPropNames
This union type will be 'prop1' | 'prop2' | 'prop3'
, corresponding to the elements of the array.
type RenderPropNames = typeof renderPropNames[number];
Define the ComponentProps Interface Using Mapped Types:
With the RenderPropNames
union type, you can construct a mapped type for the renderProps
object.
This ensures that each key in renderProps
corresponds exactly to one of the specified prop names and that each key maps to a function returning a JSX.Element
.
renderPropNames: readonly RenderPropNames[];
renderProps: { [K in RenderPropNames]: () => JSX.Element };
}
Implement the Component
ensuring it accepts props conforming to the ComponentProps
interface. The component will iterate over the renderPropNames
array and invoke the corresponding render functions from the renderProps
object.
for eg.
const Component: React.FC<ComponentProps> = ({ renderPropNames, renderProps }) => (
<div>
{renderPropNames.map(name => (
<div key={name}>
{renderProps[name]()}
</div>
))}
</div>
);
lastly, Use the Component with Type Safety:
This strict type checking ensures consistency between the declared prop names and the provided render functions. like,
<Component
renderPropNames={renderPropNames}
renderProps={{
prop1: () => <div>Prop 1</div>,
prop2: () => <div>Prop 2</div>,
prop3: () => <div>Prop 3</div>,
}}
/>
Hope this helps!!!
Modify your ComponentProps interface to use a generic type that infers the keys from renderPropNames:
interface ComponentProps<T extends string[]> {
renderPropNames: T;
renderProps: { [K in T[number]]: () => JSX.Element };
}
const Component = <T extends string[]>({ renderPropNames, renderProps }: ComponentProps<T>) => {
return (
<div>
{renderPropNames.map((name) => (
<div key={name}>{renderProps[name]()}</div>
))}
</div>
);
};
// Usage
<Component
renderPropNames={["prop1", "prop2", "prop3"]}
renderProps={{
prop1: () => <div>Prop 1</div>,
prop2: () => <div>Prop 2</div>,
prop3: () => <div>Prop 3</div>,
}}
/>;
You can try this one, it will help you.