Type '(props: Props) => Element[]' is not assignable to type 'FunctionComponent<Props>' - reactjs

I am trying to add TypeScript in a React app.
Versions:
"react": "16.9.0",
"typescript": "3.5.3",
I have an array like
import aLogo from '../images/a.svg';
import bLogo from '../images/b.svg';
const websites = [
{
name: 'A',
src: aLogo,
url: 'https://a.com',
},
{
name: 'B',
src: bLogo,
url: 'https://b.com',
},
];
I am passing it to a component through props.
interface Website {
name: string;
src: string;
url: string;
}
interface Props {
websites: Website[];
}
const SocialList: React.FC<Props> = (props: Props) => {
const { websites } = props;
return websites.map((website) => {
const { name, src, url } = website;
return (
<a key={name} href={url}>
<img src={src} />
</a>
);
});
};
But it gives me error
TypeScript error in /SocialList.tsx(16,7):
Type '(props: Props) => Element[]' is not assignable to type 'FunctionComponent<Props>'.
Type 'Element[]' is missing the following properties from type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)>': type, props, key TS2322
14 | }
15 |
> 16 | const SocialList: React.FC<Props> = (props: Props) => {
| ^
I read the answers in how do you declare an array of objects inside typescript?, but still cannot figure out how to fix it.

Your SocialList component renders multiple nodes. While this behavior is supported in React version >=16.0, it is not allowed by the type declarations for React (#types/react). In other words, you can render multiple nodes in JS React (v16+), but not in TS React. For JS React, see the related question: Return multiple elements inside React.render().
To fix this, you have a few options: either update your component so that it only returns a single node, use a type assertion, or disable the error from TypeScript.
Returning a Single Node
You can update your code to return the a tags within a containing element. The default used to be to use div, but this clutters the DOM. Instead you can use the React.Fragment component, which does not render anything to the actual DOM, and is therefore basically what you're after.
Example:
const SocialList: React.FC<Props> = (props: Props) => {
const { websites } = props;
const websiteElements = websites.map((website) => {
const { name, src, url } = website;
return (
<a key={name} href={url}>
<img src={src} />
</a>
);
});
return (
<React.Fragment>
{websiteElements}
</React.Fragment>
)
};
You can also use the short syntax for fragments as below if you prefer:
<>
{websiteElements}
</>
Type Assertion
You can assert the type of the return value to be any, which will cause the type error to be ignored. This will, however, conflict with the eslint rule no-explicit-any if you have that enabled (which is a rule I disagree with). Anyway I don't recommend this approach because you should try to avoid as any as much as possible (and here it is possible to avoid).
Example:
const SocialList: React.FC<Props> = (props: Props) => {
const { websites } = props;
return websites.map((website) => {
const { name, src, url } = website;
return (
<a key={name} href={url}>
<img src={src} />
</a>
);
}) as any;
};
Disable the Error
Since the error is just due to the type declarations from #types/react you can suppress the TS error by putting // #ts-ignore above the errored line. You could try to find a more specific error to ignore, but I couldn't find it for this case, so be careful as this will ignore all TS errors on that line (e.g. if Props accepts a type param but is not given one).
I don't recommend this approach because disabling TS errors should be avoided as much as possible, and from the TypeScript docs:
Please note that this comment only suppresses the error reporting, and we recommend you use this comments very sparingly.
Example:
// #ts-ignore
const SocialList: React.FC<Props> = (props: Props) => {
const { websites } = props;
return websites.map((website) => {
const { name, src, url } = website;
return (
<a key={name} href={url}>
<img src={src} />
</a>
);
});
};

The error is about returning an array of JSX elements which is not a valid return type from a component.
You must return a single node, all you have to do is wrap it inside a fragment <></> or a <div></div> etc...
Also you don't need to type props parameter again
const SocialList: React.FC<Props> = ({ websites }) => (
<>
{websites.map(({ name, src, url }) => (
<a key={name} href={url}>
<img src={src} />
</a>
))}
</>
);

The accepted answer here is not correct as for the reason you get this error. React components can return arrays containing React elements as of React v16. You don't have to return a Fragment.
This is an issue with the react types package, and more specifically an issue with TS itself.
source 1
source 2
As a workaround, you can either suppress this error message, since it is erroneous in itself, or, indeed, return a Fragment

How is array of JSX elements not a valid return type? This whole conversation looks weird, because returning an array from render was added back in 2017 with the release of React#16.
T'was rather disappointing, finding this instead of a real solution.
Guess the react typing was just not updated after React#16. In my code I had to fix it like this:
type AdvFC<P> = (...args: Parameters<React.FC<P>>) => ReturnType<React.FC<P>> | ReturnType<React.FC<P>>[];
Then you can use it as follows:
const SocialList: React.AdvFC<Props> = (props: Props) => ...
No idea if that brings some side effects, so far so good.

Related

How to type a forward ref in react

I have a pretty complicated component, and I would like to type the a value of it.
I Basically would like to do this
interface Selector<V,T> {
onChange(value:V): T
}
export const Selector = forwardRef<V, T>(
(
{
onChange
}: Selector<V, T>,
ref: Select<V>
) => {
// blabla
}
)
Now, I tried to follow this complete answer : https://stackoverflow.com/a/58473012/3241192
But I couldn't manage to do any of the solution.
For exemple, if I just try this :
const Comp3 = <T>(props: Props<T> & {myRef: Ref<HTMLDivElement>})
=> <div ref={myRef}> {props.options.map(o => <p>{o.label}</p>)} </div>
const Usage3 = () => <Comp3 options={options} myRef={myRef} />
I get on the T
JSX element 'T' has no corresponding closing tag.
I also tried
onChange(value:V): T
}
export const Selector = <V, T>(
(
{
onChange,
myRef
}: Selector<V, T> & {myRef: Ref<HTMLDivElement>}
) => {
// blabla
}
)
but I get
Cannot find name 'T'.
JSX and Typescript have conflicting ideas on what <T> means. To help the parser distinguish the two replace <T> with <T,> to ensure it's treated as a type and not a JSX element.

What is the correct type for rechart's Legend content?

In an app I'm helping with there is a render of rechart charts with a legend as follows:
<Legend
content={renderLegend}
/>
renderLegend is defined as the following function:
const renderLegend = (props) => {
const { payload } = props;
return (
<ul>
{
payload.reverse().map((entry, index) => (
<li key={`item-${index}`}>
<span style={{backgroundColor: entry.color}} />
<span>{entry.payload.value}</span> {entry.value}
</li>
))
}
</ul>
);
};
This function suggests that the type for props.payload should be something like:
interface props {
payload: {
color: string;
value: string;
payload: {
value: string;
}
}[];
}
In TS, I'd like to type the function renderLegend appropriately but this type seemingly does not exist.
The property content of the Legend component takes a ContentType which translates to ReactElement | (props: Props) => Element where Props does contain a payload property but if I try and access it I find it doesn't have a value attribute.
It's important to mention that this works as is, written in Typescript with no typing. I'm trying to add typing to have better acces to this payload.
Recharts provides the type Payload. Try:
interface props {
payload: Payload[];
}
Don't forget to import if your editor doesn't do so automatically:
import type { Payload } from "recharts/types/component/DefaultLegendContent";

Typecheck error while passing an array of ReactNode to a functional component

I have a component which is passing an Array of ReactNode to another component.
const getOptions = (links: Array<Links>): Array<ReactNode> => {
const options: Array<ReactNode> = [];
links.map((link): void => {
options.push(
<Link
href={link.path}
key={link.page}
>
{link.label}
</Link>
);
});
return options;
};
<<<<<some code>>>>>>
<Dropdown dropDownOptions={getOptions(dropdownLinks)} />
My Dropdown component:
const Dropdown = ({ dropDownOptions }: any): JSX.Element => {}
so the problem I have here is when I use any as dropdown option type I get a warning:
Object pattern argument should be typed with a non-any type
But when I use Array as a type I get the following error:
Property 'dropDownOptions' does not exist on type 'any[]'
I am not sure how to resolve this.
If you're specifying a type on a destructured property, you need the same structure in the type signature as well, so instead of { dropDownOptions }: Array<ReactNode>, it needs to be
const Dropdown = ({ dropDownOptions }: { dropDownOptions: Array<ReactNode> }) => (
<div>...</div>
)
or just
const Dropdown = ({ dropDownOptions }: { dropDownOptions: ReactNode[] }) => (
<div>...</div>
)
As an unrelated note, the code uses map, but it's not an actual map as it builds the result with side effects and discards the returned array. You could rewrite it as a purely functional map like this:
const getOptions = (links: Array<Links>) =>
links.map(link => (
<Link href={link.path} key={link.page}>
{link.label}
</Link>
))
The return-type signatures will all be inferred by the compiler.
TypeScript playground

Wrapper function for JSX elements, typed via generics

Currently this function works fine
function wrapElement(elem: JSX.Element) {
return ({ ...props }) => React.cloneElement(elem, { ...props })
}
I use it like this, because this way I can get intelliSense for tailwind classes
const Btn = wrapElement(<button className="[A LOT OF TAILWIND UTILITY CLASSES]" />)
But I'm trying to get it to return same type as it receives, so I could get intelliSense for attributes on intrinsic HTML elements.
Right now inferred type is
function wrapElement(elem: JSX.Element): ({ ...props }: {
[x: string]: any;
}) => React.FunctionComponentElement<any>.FunctionComponentElement<any>
I tried some stuff and it all failed with all kinds of errors, at this point it feels like this could be to hacky, but maybe I don't understand something?
It is basically impossible to get the correct props from a JSX.Element. You can achieve the design that you want, but you should pass in the element name and the props as separate arguments rather than passing in a JSX.Element.
This code can accept an element name like 'button' or any React component. It returns a function component with the same props. I am not dropping any props from the returned component because it seems like you are using this for setting defaults rather than dropping requirements.
import React, { ComponentType, ComponentProps } from "react";
const wrapElement = <
C extends keyof JSX.IntrinsicElements | ComponentType<any>
>(
Component: C,
presetProps: Partial<ComponentProps<C>>
) => (props: ComponentProps<C>) => {
const merged: ComponentProps<C> = { ...presetProps, ...props };
return <Component {...merged} />;
};
const Btn = wrapElement("button", {
className: "[A LOT OF TAILWIND UTILITY CLASSES]"
});
const Dbl = wrapElement(Btn, { onClick: () => alert("clicked") });
const Test = () => {
return <Dbl>Click</Dbl>;
};
Typescript Playground Link
You might want to customize your merge behavior to combine className or style properties rather than overriding them.
Note: when I tried to merge the props inline like <Component {...presetProps} {...props} /> I got a weird error "Type Partial<ComponentProps<C>> & ComponentProps<C> is not assignable to type IntrinsicAttributes & LibraryManagedAttributes<C, any>." So that is why I am merging the props on a separate line and annotating the type as ComponentProps<C> instead of the inferred type Partial<ComponentProps<C>> & ComponentProps<C>.

Type '({ items }: PropsWithChildren<TodoProps>) => Element[]' is not assignable to type 'FunctionComponent<TodoProps>'

I'm learning Typescript-react and I'm stuck in this error Type '({ items }: PropsWithChildren<TodoProps>) => Element[]' is not assignable to type 'FunctionComponent<TodoProps>' and I am lost on this.
Complete error:
Type '({ items }: PropsWithChildren<TodoProps>) => Element[]' is not assignable to type 'FunctionComponent<TodoProps>'.
Type 'Element[]' is missing the following properties from type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)>': type, props, key
Link for code: sandbox repo.
Error happens on the declaration of TodoList function within the TodoList.tsx file.
Any help is appreciated. Cheers!
Code:
import React from "react";
interface Todo {
id: number;
content: string;
completed: boolean;
}
interface TodoProps {
items: Todo[];
}
// v------v here is error
const TodoList: React.FC<TodoProps> = ({ items }) => {
return items.map((item: Todo) => <div key={item.id}>{item.id}</div>);
};
export default TodoList;
Yeah, the error may sound a bit confusing - in essence it says that you can only return a single ReactElement or its equivalent JSX.Element in the function component definition, enforced by React.FC type.
React Fragments solve this limitation, so you can write TodoList in the following manner:
interface TodoProps {
items: Todo[];
}
const TodoList: React.FC<TodoProps> = ({ items }) => (
<React.Fragment>
{items.map((item: Todo) => (
<div key={item.id}>{item.id}</div>
))}
</React.Fragment>
);
Short form:
const TodoList: React.FC<TodoProps> = ({ items }) => (
<>
{items.map(({ id }) => <div key={id}>{id}</div>)}
</>
);
By the way: With pure JS, both class and function components can return multiple elements in an array as render output. Currently, TS has a type incompatibility for returned arrays in function components, so Fragments provide a viable workaround here (in addition to type assertions).
I've encountered a similar error. Eventually I noticed that I'd renamed the file incorrectly from .js to .ts instead of to .tsx when converting a component to a FunctionComponent with TypeScript.
I also got this error when I was trying to return children props from my Loading component like below.
const { loading, children } = props;
return loading ? <p>Loading ... </p> : children;
Then i realize that React is expecting only one return value(1 parent component) from its render method. Therefore I wrapped children props with React.Fragment which is denoted by <></> and that solves my problem. Below is my Loadingcomponent sample, hope that helps someone else.
import { FunctionComponent } from "react";
interface ILoadingProps {
loading: boolean;
}
export const Loading: FunctionComponent<ILoadingProps> = (props) => {
const { loading, children } = props;
return loading ? <p>Loading ... </p> : <>{children}</>;
};
My problem was that I had allowed a "TypeScript Quick Fix" in VSCode to add async to the main functional component interface.
const Welcome: React.FC<TProps> = async (props) => {
After removing it,
const Welcome: React.FC<TProps> = (props) => {
things were back to normal.

Resources