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.
CodePudding user response:
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>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
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} />)
)
}
CodePudding user response:
A few things here:
- You need to tell typescript what props to expect for the
functionalComp
. You do this by passing the props type toReact.FC
likeReact.FC<PropsTypeHere>
. That makes your props definition look like this:
export interface DemoFCProps {
itemArray: FCProps[],
functionalComp: React.FC<FCProps>
}
- You should let react render this for your, rather than calling the function component directly. That makes using
<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' }
]}
/>