Reputation: 2743
I am using a private company library for Accordion. I am using TS 4.6.4 and React 17. According to the docs it works if there are multiple children:
question
answer
question
answer
question
answer
However I get the error:
This JSX tag's 'children' prop expects a single child of type 'ReactChild | undefined', but multiple children were provided.
This error is showing up on:
<StyledAccordion onChange={onChange}>
<dt>Question(SBTi)?</dt>
<div>answer </div>
<dt>Question(SBTi)?</dt>
<div>answer </div>
StyledAccordion is a styled component:
export const StyledAccordion = styled(Accordion)``
The interface of this component includes:
export interface AccordionProps {
....
....
children?: ReactChild;
....
....
Thus I am trying to extend this interface to make it support multiple children by TypeScript... I think the typescript was not well developed here...
I am trying this way:
interface MultipleChildrenAccordion extends AccordionProps {
children?: React.ReactChild[]|React.ReactNode|undefined;
}
However I am getting:
Interface 'MultipleChildrenAccordion' incorrectly extends interface 'AccordionProps'.
Types of property 'children' are incompatible.
Type 'ReactChild[] | ReactNode' is not assignable to type 'ReactChild | undefined'.
Type 'null' is not assignable to type 'ReactChild | undefined'.ts(2430)
Your help to make it accept multiple children would be really appreciated...
Thank you in advance
EDIT: I've update the type with:
type MultipleChildrenAccordion = Omit<AccordionProps, 'children'> & {
children?: React.ReactChild[]|React.ReactNode|undefined;
}
However now on <StyledAccordion onChange={onChange}>
I get:
No overload matches this call.
Overload 1 of 2, '(props: { onChange?: ((accordion: AccordionAPI, openItems: number[]) => void) | undefined; ref?: Ref<AccordionAPI> | undefined; key?: Key | null | undefined; ... 8 more ...; iconProps?: IconProps | undefined; } & { ...; } & { ...; }): ReactElement<...>', gave the following error.
Type '{ children: Element[]; onChange: (() => void) | undefined; }' is not assignable to type '{ onChange?: ((accordion: AccordionAPI, openItems: number[]) => void) | undefined; ref?: Ref<AccordionAPI> | undefined; key?: Key | null | undefined; ... 8 more ...; iconProps?: IconProps | undefined; }'.
Types of property 'children' are incompatible.
Type 'Element[]' is not assignable to type '(ReactChild & (string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal | ReactChild[] | null)) | undefined'.
Type 'Element[]' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>> & ReactChild[]'.
Type 'Element[]' is missing the following properties from type 'ReactElement<any, string | JSXElementConstructor<any>>': type, props, key
Overload 2 of 2, '(props: StyledComponentPropsWithAs<ForwardRefExoticComponent<AccordionProps & RefAttributes<AccordionAPI>>, any, Omit<AccordionProps, "children"> & { ...; }, never, ForwardRefExoticComponent<...>, ForwardRefExoticComponent<...>>): ReactElement<...>', gave the following error.
Type '{ children: Element[]; onChange: (() => void) | undefined; }' is not assignable to type '{ onChange?: ((accordion: AccordionAPI, openItems: number[]) => void) | undefined; ref?: Ref<AccordionAPI> | undefined; key?: Key | null | undefined; ... 8 more ...; iconProps?: IconProps | undefined; }'.
Types of property 'children' are incompatible.
Type 'Element[]' is not assignable to type '(ReactChild & (string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal | ReactChild[] | null)) | undefined'.
Upvotes: 0
Views: 742
Reputation: 84957
When you extend a type, you can only make it narrower, not wider. If you were allowed to make it wider, this would basically break subtyping. Consider this example:
interface Person {
name: string;
}
const someFunction = (person: Person) => {
console.log(person.name.toUpperCase()); // Should be just fine: name is a string, strings have a toUpperCase property
}
const example: Person = {
name: 'bob'
}
someFunction(example);
Now i want to extend from Person. Typescript will allow me to make it narrower, such as by limiting the name to specific strings. This extended type can be passed into someFunction, and someFunction will still work:
interface SpecificPerson extends Person {
name: 'bob' | 'alice' | 'eve',
}
const example2: SpecificPerson = {
name: 'alice'
}
someFunction(example2);
But if i was allowed to make things broader, then someFunction would break. No longer would name be a string, so when someFunction tries to call .toUpperCase()
, an exception would be thrown
interface Entity extends Person {
name: string | number; // Typescript won't actually let me do this
}
const example3: Entity = {
name: 42,
}
someFunction(example3); // This shows a typescript error at compile time, and would throw a runtime error if it was allowed.
If you want a type that's similar to AccordionProps, but it swaps out children for a wider type, you can do that, but it will not be extending from AccordionProps. You won't be able to use it in places that expect AccordionProps:
type MultipleChildrenAccordionProps = Omit<AccordionProps, 'children'> & {
children?: React.ReactChild[]|React.ReactNode|undefined;
}
Note that Accordion can not use this type. If accordion is implemented such that it only supports a single child, then you must give it a single child.
Depending on how Accordion is implemented, maybe you could pass it a fragment:
<StyledAccordion onChange={onChange}>
<>
<dt>Question(SBTi)?</dt>
<div>answer </div>
<dt>Question(SBTi)?</dt>
<div>answer </div>
</>
</StyledAccordion>
Or if that doesn't work, give it a div:
<StyledAccordion onChange={onChange}>
<div>
<dt>Question(SBTi)?</dt>
<div>answer </div>
<dt>Question(SBTi)?</dt>
<div>answer </div>
</div>
</StyledAccordion>
Upvotes: 1