Reputation: 49694
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.
Upvotes: 34
Views: 96633
Reputation: 15652
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.
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}
</>
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;
};
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>
);
});
};
Upvotes: 78
Reputation: 9358
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.
As a workaround, you can either suppress this error message, since it is erroneous in itself, or, indeed, return a Fragment
Upvotes: 7
Reputation: 360
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.
Upvotes: 1
Reputation: 11760
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>
))}
</>
);
Upvotes: 9