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

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.

Related

Problem with typescript error when create react generic component

Recently, I encountered some issues while using React. when I called my react generic component, BaseBlock.
I had already provided a type, but TypeScript threw an error saying
Expected 0 type arguments, but got 1.ts(2558)
I would like to ask how to solve this situation.
const items = ['test1', 'test2', 'test3'];
const renderComp = (item: any) => {
return <div>{item}</div>
}
<BaseBlock<string> items={items}>{renderRow}</BaseBlock>
^
error
This is my BaseBlock component
import React from 'react';
interface Props<T> {
id?: string;
items?: T[];
children?: (item: T, index: number) => React.ReactNode;
parentId?: string;
contentId?: string[];
}
const BaseBlock = React.forwardRef(function BaseBlock<T>(
{ items, children }: React.PropsWithChildren<Props<T>>,
ref: React.ForwardedRef<HTMLDivElement>
): React.ReactElement | null {
return (
<div data-testid="base-block" ref={ref}>
{items && children && items.map(children)}
</div>
);
});
export type BaseBlockProps<T> = React.PropsWithRef<Props<T>>;
export default BaseBlock;
I give my component string type. Excepted no error and render this component. It just render, but typescript give me error
Expected 0 type arguments, but got 1.ts(2558)
I think it is that I give the <string> type for my comp, but this comp should not use type, it excepted 0 argument on this comp, so how can I solved?
I solved this problem by #Linda Paiste comment
and reference this question.
Finally, I used global type augmentation and solved it.
declare module 'react' {
function forwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}

How to pass props to react component in TypeScript? [duplicate]

This question already has an answer here:
Type of props is not assignable to type 'string'
(1 answer)
Closed last month.
So I have a react component:
function Navbar(isDark: boolean, toggle: () => void) {
...
}
Now I want to use this component in another component like so:
function App() {
return <div> <Navbar isDark={someBoolValue} toggle={someFunc} /> </div>
}
However I get this error:
Type '{ isDark: boolean; toggle: () => void; }' is not assignable to type 'IntrinsicAttributes & boolean'.
How can I fix this?
Props need to be deconstructed because they are passed as objects. You can adjust your NavBar as follows
func Navbar({isDark, toggle}:{isDark:boolean,toggle:() => void}) {
...
}
You could also create an interface
interface IProps {
isDark: boolean;
toggle: () => void;
}
func Navbar({isDark, toggle}:IProps) {
...
}
CodeSandBox : https://codesandbox.io/s/dawn-tdd-g16din?file=/src/Navbar.tsx
This is worth a read : https://stackoverflow.com/a/65948871/15474532
To expound upon the other answer, I would type it as such, which is much more explicit and strongly typed.
type NavbarProps = {
isDark: boolean;
toggle: () => void;
}
const Navbar: React.FC<NavbarProps> = ({ isDark, toggle}) => {
...
}
React is expecting a functional component to only have one parameter, which is a object containing the props. To immediately fix you problem, you could change this to
function Navbar(props: {isDark: boolean, toggle: () => void}) {
...
}
and then access the props in the function as props.isDark or props.toggle. For readability, you may also consider writing this as
interface NavbarProps {
isDark: boolean;
toggle: () => void;
}
function Navbar({isDark, toggle}: NavbarProps) {
...
}
Generally good practice to put component props in interfaces in the same spot in every file for quick reference.

Type argument at moment of use

I'm trying to build a generic tool for handling the response to React Query useQuery requests in my app, and build this out using TypeScript, so that the data is correctly typed.
In a parent component, the use case would be something like this:
const queryResult = useQuery<MyResponseType, Error>('whatever');
return (
<ReactQueryLoader useQueryResult={queryResult}>
{data => {
// Data should be of MyResponseType type, but is unknown
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}}
</ReactQueryLoader>
);
I tried defining this ReactQueryLoader as so:
import React from "react";
import { UseQueryResult } from "react-query";
import { Loading } from "./Loading";
interface ReactQueryLoaderProps {
useQueryResult: UseQueryResult<unknown, Error>;
handleError?: (error: Error) => void;
children: (data: unknown) => JSX.Element;
}
export const ReactQueryLoader = ({
useQueryResult,
handleError = error => {
console.error(error);
throw new Error(error.message);
},
children,
}: ReactQueryLoaderProps): null | JSX.Element => {
if (useQueryResult.isError) {
handleError(useQueryResult.error);
return null;
}
if (useQueryResult.isLoading) {
return <Loading />;
}
return children(useQueryResult.data);
};
But when I inspect the data passed to the child, instead of being the type of the expected response data, it is unknown. I'm having trouble getting this to work, as someone with a mid-low-level understanding of TypeScript. I thought I could apply the same pattern as in this answer to another question I had, but I can't figure out how to do it in this context.
I tried this:
interface ReactQueryLoaderProps {
// ...
children: <T extends unknown>(data: T) => JSX.Element;
}
But got this in the parent element:
(parameter) data: T extends unknown
I also tried:
interface ReactQueryLoaderProps {
useQueryResult: UseQueryResult<T extends unknown, Error>;
But that provoked two errors:
Under the T: Cannot find name 'T'.
Under the comma: '?' expected.
Is this possible to do?
The ReactQueryLoader function needs to be generic here, as well as the props type.
That might look something like this:
interface ReactQueryLoaderProps<T extends UseQueryResult<unknown, Error>> {
useQueryResult: T;
handleError?: (error: Error) => void;
children: (data: T['data']) => JSX.Element;
}
And
export function ReactQueryLoader<T extends UseQueryResult<unknown, Error>>({
useQueryResult,
handleError,
children,
}: ReactQueryLoaderProps<T>): null | JSX.Element {
//...
}
Here ReactQueryLoader is now a generic function that captures the query result type as T and passes that to the generic props type ReactQueryLoaderProps.
ReactQueryLoaderProps then uses that type as the type of useQueryResult, and pulls the ['data'] property from that type for the type of the children function parameter.
See playground

Typescript - auto inherit props type in React callback

Having this implementation
// CustomComponent.ts
type SaveProps = {
date: string;
name: string;
}
interface IProps {
onSave: (props: SaveProps) => void;
}
const CustomComponent = ({onSave}: IProps) => {
return (
<button onClick={() => onSave({ date: '', name: '' })}>
Click me
</button>
);
};
// ParentComponent.ts
import CustomComponent from './CustomComponent';
export default function ParentComponent () {
const saveData = props => { // what type is props? how to infer this as SaveProps defined in CustomComponent.ts?
}
return (
<CustomComponent onSave={saveData} />
)
}
How can I infer the type of props in saveData as being Props without having to import the type? So that when reading the props in saveData callback, I would automatically know what kind of data CustomComponent is sending back.
In most cases, it is better to export Props type along with the component. In your particular case you can do the following:
import { IProps, CustomComponent } from './CustomComponent';
function ParentComponent () {
const saveData = React.useCallback((props => {
}) as IProps['onSave'], []);
return (
<CustomComponent onSave={saveData} />
)
}
TS Playground
Sorry for one more answer. Comments are restricted by max length.
https://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-typing
TS is smart enough to infer types in different directions, And its managed by a bunch of heuristic algorithms. That's why we can not refactor your example according to some generic ruleset (there is no such).
One more trick without explicit export of Props:
(props => {
}) as Parameters<typeof CustomComponent>[0]['onSave']
TS Playground

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

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.

Resources