Problem with typescript error when create react generic component - reactjs

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;
}

Related

Fixing types for mapping over object to create React components

I really like uing this pattern for rendering similar components and using a _type prop to distinguish it and pass it down to the correct component.
However, I've found it difficult to add in the types correctly and was wondering if you guys could help. I have some questions;
Is the BlockMap type correct?
What type should I be using for ResolvedBlock?
Or generally, is there a better way of writing the types (without changing this structure?)
import React from 'react'
import { ImageBlock } from '/ImageBlock' // Assume all components are imported from whereever
type BlockType =
| 'imageBlock'
| 'formBlock'
| 'statisticBlock'
| 'videoBlock'
| 'quoteBlock'
interface Block {
_type: BlockType
_key: string
heading?: string
backgroundColor?: string
theme?: 'dark' | 'light'
}
type BlockMap = Record<BlockType, JSX.Element> // Is this type correct?
const blockMap:BlockMap = {
imageBlock: ImageBlock,
formBlock: FormBlock,
statisticBlock: StatisticBlock,
videoBlock: VideoBlock,
quoteBlock: QuoteBlock,
}
interface Props {
className?: string
blocks: Block[]
}
export function BlocksBuilder({
blocks = [],
className = ``,
}: Props):JSX.Element {
return (
<>
{blocks.map(block => {
const ResolvedBlock = blockMap[block._type] // What type should ResolvedBlock be?
if (!ResolvedBlock) return null
return (
<ResolvedBlock
className={className}
block={block}
key={block._key}
/>
)
})}
</>
)
}
It's a good pattern, and your type is close, but you will want to define the shape of your components instead.
type BlockMap = Record<BlockType, (props: any) => JSX.Element>
You could also define the props for Block components, and use that type for each of your components
interface BlockProps {
key: string;
block: Block;
classname: string;
}
type BlockComponent = (props: BlockProps) => JSX.Element;
export const ImageBlock: BlockComponent = (props) => {
return <></>;
};
type BlockMap = Record<BlockType, BlockComponent>

How to correctly type a generic React function component in TypeScript

In the following TypeScript Playground example I tried to generalise the function component Element into a GenericElement component but TypeScript complains about the syntax.
How to correctly type a generic react function component in TypeScript using the React.FC type definition approach?
import React from 'react';
type PropsType = {
id: string,
value: string,
};
type GenericPropsType<keyType> = {
id: keyType,
value: string,
};
const Element: React.FC<PropsType> = ({ id, value }) => {
return <div>{id.toString()}={value}</div>;
};
const GenericElement: React.FC<GenericPropsType<keyType>> = <keyType = string>({ id, value }) => {
return <div>{id.toString()}={value}</div>;
};
Type 'Element' is not assignable to type 'FC<GenericPropsType<any>>'.
Type 'Element' provides no match for the signature '(props: PropsWithChildren<GenericPropsType<any>>, context?: any): ReactElement<any, any> | null'.
Cannot find name 'keyType'.
Property 'keyType' does not exist on type 'JSX.IntrinsicElements'.
Cannot find name 'id'.
Left side of comma operator is unused and has no side effects.
Cannot find name 'value'.
Cannot find name 'id'.
Cannot find name 'value'.
Identifier expected.
Unexpected token. Did you mean `{'>'}` or `>`?
Expression expected.
Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
JSX element 'keyType' has no corresponding closing tag.
'</' expected.
'Element' is declared but its value is never read.
I think it works using Higher-order function:
import React, { FC } from 'react';
type GenericPropsType<T = any> = {
id: T,
value: string,
};
const HoC = <T,>(): FC<GenericPropsType<T>> => (props) => <div></div>
const WithString = HoC<string>() // React.FC<GenericPropsType<string>>
Drawback: you have function overhead only because of type
I don't believe my answer is helpful, because you should explicitly define generic type, or you need to pass an argument in order to infer it:
const HoC = <T,>(a:T): FC<GenericPropsType<T>> => (props) => <div></div>
const WithString = HoC('a') // React.FC<GenericPropsType<string>>
Otherwise, I suggest you to use #Aleksey L.'s solution.
Based on the explanation of #Aleksey L., I came to the following complete example that might be helpful to others:
import React from 'react';
import ReactDOM from 'react-dom';
type GenericPropsType<keyType> = {
id: keyType,
value: string,
};
const GenericElement = <keyType extends string | number = string>({
id,
value = ''
}: GenericPropsType<keyType>): JSX.Element => {
return (<div>{id}={value}</div>);
};
const UseGenericElement: React.FC = () => {
return (<div><GenericElement id={4711} value="4711" /></div>);
};
ReactDOM.render(
<div><UseGenericElement /></div>,
document.getElementById('root');
);

Type 'Element[]' is missing the following properties when handling react children

I have a parent component that provides its children to a child component. The children can either be a node or an array of nodes, and depending on if it is a node or an array of nodes the children in the ChildComponent are rendered differently. However, when I a render the ChildComponent in the ParentComponent, I get the following Typescript error:
TS2786: 'ChildComponent' cannot be used as a JSX component.   Its return type 'Element | Element[]' is not a valid JSX element.     Type 'Element[]' is missing the following properties from type 'Element': type, props, key
Please see the below for the code. How can I render the ChildComponent without any Typescript errors? Thanks!
import React from 'react';
import styles from './component.scss';
interface Props {
children: React.ReactChildren;
}
const ChildComponent = ({ children }: Props): JSX.Element | JSX.Element[] => {
if (React.Children.count(children)) {
return React.Children.map(children, (child) => (
<div className={styles.childContainer}>{child}</div>
));
}
return <div className={styles.singleContainer}>{children}</div>;
};
const ParentComponent: React.FC<Props> = ({ children }) => (
<div className={styles.container}>
<ChildComponent>{children}</ChildComponent>
</div>
);
export default ParentComponent;
In your current code, the return type is JSX.Element[] from if block (incorrect) but JSX.Element (correct) from else part. That's the reason of the error you are seeing.
To fix it, you need to return single JSX expression from ChildComponent as "JSX expressions must have one parent element".
You can use a parent element like div, span or simply use React.Fragment (shorthand <></>):
const ChildComponent = ({ children }: Props) => {
if (React.Children.count(children)) {
return (
<> // HERE
{React.Children.map(children, (child) => (
<div className={styles.childContainer}>{child}</div>
))}
</>
);
}
return <div className={styles.singleContainer}>{children}</div>;
};
And children type should be ReactNode instead of ReactChildren:
interface Props {
children: React.ReactNode;
}
As these are the definitions of both the typings:
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
interface ReactChildren {
map<T, C>(children: C | C[], fn: (child: C, index: number) => T):
C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
forEach<C>(children: C | C[], fn: (child: C, index: number) => void): void;
count(children: any): number;
only<C>(children: C): C extends any[] ? never : C;
toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
}
Also, I would suggest not to use React.FC.
Here is a demo.
Edit:
PS: The else block <div className={styles.singleContainer}>{children}</div> and the if condition with count is not really required.

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.

What is the TypeScript return type of a React stateless component?

What would the return type be here?
const Foo
: () => // ???
= () => (
<div>
Foobar
</div>
)
StatelessComponent type mentioned in this answer has been deprecated because after introducing the Hooks API they are not always stateless.
A function component is of type React.FunctionComponent and it has an alias React.FC to keep things nice and short.
It has one required property, a function, which will return a ReactElement or null. It has a few optional properties, such as propTypes, contextTypes, defaultProps and displayName.
Here's an example:
const MyFunctionComponent: React.FC = (): ReactElement => {
return <div>Hello, I am a function component</div>
}
And here are the types from #types/react 16.8.24:
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
interface ISomeCoolInterface {
some: 'string';
cool: 'string';
props: 'string'
}
const SomeCoolComponent
: React.FC<ISomeCoolInterface>
= ({ some, cool, props }): JSX.Element => {
return <SomeCoolComponent>{some, cool, props}</SomeCoolComponent>
}
The important bit here being the return type JSX.Element
The correct return type here is ReactElement<P>, but a better option would be to use React.StatelessComponent<P> like this
const Foo
: React.StatelessComponent<{}>
= () => (
<div>
Foobar
</div>
)
If using the function keyword, the best return type appears to be JSX.Element | null.
For now our team is using JSXNode as shorthand, since these are the only two types that can be returned directly as a JSX result:
type JSXNode = JSX.Element | null;
Edit: looks like eventually React.ReactNode is the intended return type for JSX but it's currently not possible. (Reference)
Background:
None of the answers here seem to address the most common modern case - that you have a function returning an element. What type should this return?
function MyComponent(): SomeTypeHere {
return <>...</>;
}
The recommended way to hide the component is to return null, so it's not clear what clean return type that would be. Typing JSX.Element | null everywhere or even making a custom type like that seems it should be unnecessary given how universal this case is. ReactNode also doesn't work because undefined can't be returned as JSX.
Overall the best return type seems to be JSX.Element | null. That is the return type of the FC type which is used if you're not using the function keyword:
const MyComponent: FC = () => { <>...</> }
I would also add .SFC, which stands for Stateless Functional Component.
const Foo
: React.SFC<{}>
= () => (
<div>
Foobar
</div>
)
See https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts
Each JSX element is just syntactic sugar for calling React.createElement(component, props, ...children).
function createElement<P extends DOMAttributes<T>, T extends Element>(
type: string,
props?: ClassAttributes<T> & P,
...children: ReactNode[]): DOMElement<P, T>;
So it's DOMElement<P, T>

Resources