Reputation: 105
In React with TypeScript, I am trying to pass a functional component and an array of props to said functional component as params to a container functional component that will display them.
I am just learning Typescript and React.
Here is how I thought this would work:
import React from 'react';
export interface FCProps{
[key: string]: any
}
export interface DemoFCProps {
itemArray: FCProps[],
functionalComp: React.FC
}
export default function DisplayFCs(props : DemoFCProps) {
return (
<div>
{
// for each item in the array
props.itemArray.map(
// spread that item's properties as props to the functional component
(item)=>(props.functionalComp(...item))
)
}
</div>
)
}
The hope was that this would allow me to call the DisplayFCs component with any component and array of props.
import React from 'react';
import DisplayFCs from './DisplayFCs';
function Task (name: string, content: string) {
return(
<div>
<p>{name}</p> <p>{content}</p>
</div>
)
};
const taskPropsArray= [
{name: "jog", content: "Run in park"},
{name: "walk dog", content: "Give Treat when done"},
]
function Project(name: string, status: string){
return (
<div>
<p>{name}</p><p>{status}</p>
</div>
)}
const projectPropsArray =[
{name: 'Program', status: "active" }, {name: "Pilot", status:"inactive"},
]
export default function DemoProblem(){
return(
<>
<DisplayFCs functionalComp={Task} propsArray={taskPropsArray} />
<DisplayFCs functionalComp={Project} propsArray={projectPropsArray} />
</>
)
}
In case this is relevant, I am doing it this way because the website I am building has a lot of places where I need to allow for UI to filter lists. This is a simplified version of the problem where I can pass a component and a filter object and have the controls for filtering those lists appear dynamically without having to repeatedly copy and paste code.
Upvotes: 0
Views: 3712
Reputation: 186994
A few things here:
functionalComp
. You do this by passing the props type to React.FC
like React.FC<PropsTypeHere>
. That makes your props definition look like this:export interface DemoFCProps {
itemArray: FCProps[],
functionalComp: React.FC<FCProps>
}
<MyComponent {...props} />
syntax.export default function DisplayFCs(props : DemoFCProps) {
const FunctionalComp = props.functionalComp
return (
<div>
{
props.itemArray.map(
(item) => <FunctionalComp {...item} />
)
}
</div>
)
}
But this isn't all the way to your goal:
The hope was that this would allow me to call the DisplayFCs component with any component and array of props.
To do that, DisplayFCs
needs to be generic. Let's start with it's props.
export interface DisplayFCsProps<
FCProps extends { [key: string]: unknown }
> {
itemArray: FCProps[],
functionalComp: React.FC<FCProps>
}
Here FCProps
is a generic type parameter. Whatever that type is, this constructs a type where it requires an array of that type, and a functional component that takes that type as props.
Then you need to use that props type from a generic functional component:
export default function DisplayFCs<
Props extends { [key: string]: unknown }
>(props: DisplayFCsProps<Props>) {
const FunctionalComp = props.functionalComp
return (
<div>
{
props.itemArray.map(
(item) => <FunctionalComp {...item} />
)
}
</div>
)
}
Which now works as you would expect
function Person(props: { name: string, age: number }) {
return <></>
}
const testPersons = <DisplayFCs
functionalComp={Person}
itemArray={[
{ name: 'Sue', age: 30 },
{ name: 'joe', age: 24 }
]}
/>
function Book(props: { title: string, author: string }) {
return <></>
}
const testBooks = <DisplayFCs
functionalComp={Book}
itemArray={[
{ title: 'Necronomicon', author: 'Sue' },
{ title: 'The Expanse', author: 'Joe' }
]}
/>
Upvotes: 1
Reputation: 14355
First, I want to clarify what a function component is. Neither Task
or Project
are function components. They look like they are, but they aren't.
Most importantly to your question, a function component accepts one parameter - props
, and is used in either JSX syntax (<MyComp/>
) or using React.createElement
calls (pretty rare).
Your "Function components" are just regular functions right now that happen to return JSX.
Next, itemArray
defined in the interface of DisplayFCs
does not match your usage where you pass it propsArray
.
Finally, as shown in the snippet below, you should convert the two functions to be true components, and call them with JSX. Since the prop functionalComp
does not begin with an uppercase letter, notice that I assigned it to a temporary variable that does. I've removed the typings for the snippet to run.
function DisplayFCs(props) {
// Temp variable to make uppercase
const Comp = props.functionalComp;
return (
<div>
{props.propsArray.map((item) => {
return <Comp {...item} />
})}
</div>
)
}
// Use props and access values through them
function Task (props) {
return(
<div>
<p>{props.name}</p> <p>{props.content}</p>
</div>
)
};
const taskPropsArray = [
{name: "jog", content: "Run in park"},
{name: "walk dog", content: "Give Treat when done"},
];
// Use props and access values through them
function Project(props) {
return (
<div>
<p>{props.name}</p><p>{props.status}</p>
</div>
)
}
const projectPropsArray =[
{name: 'Program', status: "active" },
{name: "Pilot", status: "inactive"},
]
function DemoProblem(){
return(
<div>
<h1>Title</h1>
<DisplayFCs functionalComp={Task} propsArray={taskPropsArray} />
<DisplayFCs functionalComp={Project} propsArray={projectPropsArray} />
</div>
)
}
ReactDOM.render(<DemoProblem />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 0
Reputation: 920
I could be wrong but I guess you just need to change your DisplayFCs
's map:
{
// for each item in the array
props.itemArray.map(
// spread that item's properties as props to the functional component
(item)=>(<props.functionalComp {...item} />)
)
}
Upvotes: 0