I need to make Accordion component reusable. In the future I should be able to add new object to the same array ‘accordionProps’ and pass it down to accordion.
Here I’m passing an array ‘accordionProps’ of objects down to component. Prop types are defined in child component Accordion which matches my array content.
const accordionProps = [ { title: 'Active Orders', body: <OrderModule />, }, { title: 'Drivers', body: <DriverModule />, }, ]; return ( <DashboardWrapper> <Accordion title={title} body={body}/> <Map /> </DashboardWrapper> ); }; export default Dashboard;
Still TypeScript throws an error:
Type ‘{ accordionProps: { title: string; body: Element; }[]; }’ is not assignable to type ‘IntrinsicAttributes & accordionProps’. Property ‘accordionProps’ does not exist on type ‘IntrinsicAttributes & accordionProps’.
It doesn’t work when I do pass props as {…accordionProps} either.
Here is what my child component Accordion looks like:
import { Component } from 'react'; type accordionProps = [ { title: string; body: Component; }, ]; const Accordion = (props: accordionProps) => { return ( <AccordionContainer> {props.map(section => ( <div key={section.title}> <div>{section.title})</div> <div>{section.body}</div> </div> ))} </AccordionContainer> ); }; export default Accordion;
I can’t figure why TS is not letting me do that.
Does anyone know if an abstraction like that is even possible in React?
Help is really appreciated.
Advertisement
Answer
You declared your prop types as an array with one object in it:
type AccordionProps = [ { title: string; body: Component; }, ];
However, react props must be an object:
type AccordionProps = { title: string; body: Component; },
Then, in order to render an array of items, you need to map over the array of props, rendering the component you want in each iteration of the loop.
For example, assuming you had:
// declare an array of AccordionProps objects const accordionProps: AccordionProps[] = [ { title: 'Active Orders', body: <OrderModule />, }, { title: 'Drivers', body: <DriverModule />, }, ];
Then you might render those like so:
<> { accordionProps.map(props => <Accordion {...props} />) } </>
Update from your complete example:
React components always take an object for props, never an array. One of those props may be an array, but the props as a whole must be an object. So props.map
is never right.
So first, let’s fix your Accordion
component:
type AccordionSection = { title: string; body: ReactNode; }; type AccordionProps = { sections: AccordionSection[]; }; const Accordion = (props: AccordionProps) => { return ( <div> {props.sections.map((section) => ( <div key={section.title}> <div>{section.title})</div> <div>{section.body}</div> </div> ))} </div> ); };
Here props
is an object with a single property sections
. sections
is type to be an array of AccordionSection
objects. That means you can iterate through those sections with props.sections.map
.
Also note that I change the type of body
from Component
to ReactNode
. Component
is a type for renderable components, where ReactNode
is the type for already rendered content. function MyComponent() { return <></> }
is a component. <MyComponent />
is a ReactNode
.
Now when you call this component, you have to pass in the sections
prop as an array of AccordionSection
objects.
import Accordion, { AccordionSection } from "../Accordion"; export default function App() { const accordionSections: AccordionSection[] = [ { title: "Active Orders", body: <OrderModule /> }, { title: "Drivers", body: <DriverModule /> } ]; return ( <div className="App"> <Accordion sections={accordionSections} /> </div> ); }