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

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>

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

React TypeScript - Pass a dynamic generic type into a forwardRef component

Core of my question
const FinalComponent<GenericType extends 'a' | 'b'> = is invalid tsx syntax.
// The 1st line here is invalid tsx syntax
const FinalComponent<InvalidGenericType extends 'a' | 'b'> =
forwardRef<HTMLParagraphElement, PropsWithStandardRef<InvalidGenericType>>(({ value }, ref) => {
return <Component forwardedRef={ref} value={value} />
}) as ComponentType<InvalidGenericType>
Intended usage of the component:
const ExampleUsage = () => <FinalComponent<'b'> value="b" />
How do I make a generic type in this situation?
Additional context
For additional context, here is the rest of the code:
import { Ref, forwardRef } from 'react'
// These are the base props for the component.
// In terms of usage, these are the props that I care about.
interface Props<GenericType extends 'a' | 'b'> {
value: GenericType
}
// Adding forwardedRef to the props to define what props are usable inside the component
interface PropsWithForwardRef<GenericType extends 'a' | 'b'> extends Props<GenericType> {
forwardedRef: Ref<HTMLParagraphElement | null>
}
// Adding standard ref to the props to define what props the component can accept from outside
interface PropsWithStandardRef<GenericType extends 'a' | 'b'> extends Props<GenericType> {
ref?: Ref<HTMLParagraphElement | null>
}
// forwardRef is interfering with the inheritance of the generic types.
// This is a stand in for the expected return type of the component.
type ComponentType<GenericType extends 'a' | 'b'> = (props: PropsWithStandardRef<GenericType>) => JSX.Element
// The core component code
function CoreComponent<GenericType extends 'a' | 'b'> ({ value, forwardedRef }:PropsWithForwardRef<GenericType>):JSX.Element {
return <p ref={forwardedRef}>{value}</p>
}
// !!!!!!!!!!! IMPORTANT BIT !!!!!!!!!!!!
// This is where my problem is, I need to be able to pass a dynamic generic type into PropsWithStandardRef and ComponentType.
// I'm not sure how to do that though because `const FinalComponent<InvalidGenericType extends 'a' | 'b'> = forwardRef()` is invalid
const FinalComponent<InvalidGenericType extends 'a' | 'b'> = forwardRef<HTMLParagraphElement, PropsWithStandardRef<InvalidGenericType>>(({ value }, ref) => {
return <CoreComponent forwardedRef={ref} value={value} />
// I need the `as ComponentType<InvalidGenericType>` bit because the inferred type that comes out of forwardRef
// is making TS lose the generic types information
}) as ComponentType<InvalidGenericType>
// This is the end goal of how I want to be able to use this component
// I want to be able to pass a generic type into the component without TS complaining
const ExampleUsage = () => <FinalComponent<'b'> value="b" />
PS. I recognize that this example is a bit contrived, it is for the sake of simplifying my real world problem which features a far more complex component.
Similar but different question
This is different to React Typescript - dynamic types
In that question, it doesn't require passing the type information into the variable, more just changing what the type is based on what values the user provides.
I need the end use of the component to be able to pass a type into it.
Your provided code is very close to working, but there are a just a few things which need to change for type correctness:
Refs which hold HTML elements should not be mutable/nullable, because they are set and managed by React, and the Ref<T> util already includes null anyway. (This is unless you're doing something truly exotic like imperatively manipulating elements outside the render tree... but I've never even seen that in a codebase.) Because of this, I removed null from your union in Ref<HTMLParagraphElement | null>. (This was also causing a problem with passing the value to the actual paragraph element in CoreComponent.)
The return type for your ComponentType needs to include null for the return type of forwardRef to be assignable to it. Speaking of return types for functions which return React elements, JSX.Element is simply an alias to ReactElement with any passed in the type params. I changed the JSX.Element references to ReactElement.
A type annotation can still be applied to a variable that holds a function expression value. It is written the same way any other annotation is written: following the identifier name, like this:
const add: (...numbers: number[]) => number = (...nums) => nums.reduce((sum, n) => sum + n, 0);
The syntax above is not easy to read in my opinion, so I prefer wrapping the type in parentheses for readability. You can even utilize generics with the signature (needed in your case), and overloading is possible as well. See the FinalComponent in your code below, modified:
TS Playground link
import {
default as React,
createRef,
forwardRef,
ReactElement,
Ref,
} from 'react';
type AorB = 'a' | 'b';
type Props<T extends AorB> = { value: T };
type PropsWithForwardRef<T extends AorB> = Props<T> & { forwardedRef: Ref<HTMLParagraphElement> };
type PropsWithStandardRef<T extends AorB> = Props<T> & { ref?: Ref<HTMLParagraphElement> };
function CoreComponent<T extends AorB> ({ value, forwardedRef }:PropsWithForwardRef<T>): ReactElement {
return <p ref={forwardedRef}>{value}</p>;
}
const FinalComponent: (<T extends AorB>(props: PropsWithStandardRef<T>) => ReactElement | null) =
forwardRef<HTMLParagraphElement, Props<AorB>>(({ value }, ref) => <CoreComponent forwardedRef={ref} value={value} />);
/**
* The annotation for the function expression above can also be written this way,
* which allows for overloading with multiple signatures, one on each line inside the braces:
*/
// const FinalComponent: {
// <T extends AorB>(props: PropsWithStandardRef<T>): ReactElement | null;
// } = forwardRef<HTMLParagraphElement, Props<AorB>>(({ value }, ref) => <CoreComponent forwardedRef={ref} value={value} />);
/* Use: */
const ref = createRef<HTMLParagraphElement>();
const ExampleA = () => <FinalComponent<'a'> value="a" ref={ref} />;
const ExampleB = () => <FinalComponent<'b'> value="b" ref={ref} />;
const RefOptional = () => <FinalComponent<'a'> value="a" />;
const NoRestrictionA = () => <FinalComponent value="a" ref={ref} />;
const NoRestrictionB = () => <FinalComponent value="b" />;
const InvalidA = () => <FinalComponent<'a'> value="b" ref={ref} />;
const InvalidNotAorB = () => <FinalComponent value="c" />;
const InvalidNoValue = () => <FinalComponent<'a'> />;
Variable can't use generic. define it as FC:
const FinalComponent: <InvalidGenericType extends 'a' | 'b'>(
props: PropsWithStandardRef<InvalidGenericType>
) => JSX.Element = forwardRef<
HTMLParagraphElement,
PropsWithStandardRef<'a' | 'b'>
>(({ value }, ref) => {
return <CoreComponent forwardedRef={ref} value={value} />
}) as ComponentType<'a' | 'b'>
Or use interface to define it:
interface FinalComponentType
extends React.ForwardRefExoticComponent<PropsWithStandardRef<'a' | 'b'>> {
<GenericType extends 'a' | 'b'>(
props: PropsWithStandardRef<GenericType>
): JSX.Element
}
const FinalComponent = forwardRef<
HTMLParagraphElement,
PropsWithStandardRef<'a' | 'b'>
>(({ value }, ref) => {
return <CoreComponent forwardedRef={ref} value={value} />
}) as FinalComponentType
Works fine:
const ExampleUsage = () => <FinalComponent<'a'> value="b" /> // There will be error
I've encountered this problem a number of times and while this is not a canonical answer this is how I usually resolve it:
You basically need your react function component to accept a generic type as the type parameter. The only way I found to deal with this was to create the function component without explicitly declaring it as a function component. The problem is:
const FinalComponent : React.FC<Props<GenericType<...>> = // There's no type variable that you can use at that point
To bypass this what I do is:
const FinalComponent = <T extends 'a'|'b'>(props: Props<T>): ReturnType<React.FC<Props<T>>> => null; //Return whatever you need here
This will declare a generic react function component that you can use like below:
const Res = () => <FinalComponent<'a'> value='a' />; // Works
const Res2 = () => <FinalComponent<'a'> value='b' />; // Errors
Random link

Typescript how to make a fucntion optional when its passed via props

I'm trying to pass a function through props but I'd like it to be optional. However, if I try to make it optional in the props interface by putting a "?" in front of the function, it gives me this error:
'propsFunction', which lacks return-type annotation, implicitly has an 'any' return type.
Is there any way to make a function optional to pass via props?
interface Props {
users: Array<User> | undefined;
propsFunction(userTo:string): void;
}
const Component: React.FC<Props> = (props) => {
You can use arrow function syntax to define the type
interface Props {
users: Array<User> | undefined;
propsFunction?: (userTo: string) => void;
}
const Component: React.FC<Props> = (props) => {

Why React.FC doesn't allow me to just return children?

While trying to create a component I realized a situation.
Only returning children
interface FooProps {
children?: React.ReactNode
}
const Foo: React.FC<FooProps> = ({ children }) => {
return children
}
Will give me an error saying:
Type '({ children }: PropsWithChildren<FooProps>) => ReactNode' is not assignable to type 'FC<FooProps>'.
Type 'ReactNode' is not assignable to type 'ReactElement<any, any> | null'.
Type 'undefined' is not assignable to type 'ReactElement<any, any> | null'.
But if I return children inside any jsx, or even Fragment:
const Foo: React.FC<FooProps> = ({ children }) => {
return <>{children}</>
}
It won't give me any error.
The obvious answer to this is that the types are incompatible, ReactNode is not assignable to type ReactElement<any, any> | null, as the error says, but my question is Why?
Why returning a ReactNode (e.g. children) isn't allowed? Shouldn't it be allowed?
Something extra to ask would be if this is something about React.FC and probably other type will be ok if I return ReactNode or if this is with all react components?
Because React.ReactNode type is a union type. Let's see what the type definition for it looks like,
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
Expected return type when you create a functional component with generic FC
ReactElement<any, any> | null
So when you return the children directly from the component the compiler complains because the types are simply not compatible. The reason being ReactElement is a valid React.ReactNode but not the other way around, because React.ReactNode can also be a value of type ReactFragment or ReactPortal and so on.
It is obvious that it will create a type mismatch with ReactElement. But when you return the children inside a Fragment the compiler no longer complains because the return type becomes valid. Take a look at this example without the FC generic,
// The return type of Foo is inferred as React.ReactNode
// Compiler doesn't complain because we don't annotate the return type
const Foo = ({ children }: PropsWithChildren<FooProps>) => {
return children;
};
// But when you use React.FC generic it is the same as annotating the return type
// of Foo as `React.ReactElement`
// compiler will complain because of the type mismatch
const Foo = ({ children }: PropsWithChildren<FooProps>): React.ReactElement => {
return children;
};
Example for why it works when you return the children inside a Fragment
// Without generic FC
// Return type infered as JSX.Element which simply extends React.ReactElement
const Foo = ({ children }: PropsWithChildren<FooProps>) => {
return <>children</>;
};
// With generic FC
// Compiler doesn't complain because of JSX.Element can be valid-
// return type because it simply extends the interface React.ReactElement
const Foo: FC<FooProps> = ({ children }) => {
return <>children</>;
};
Edit - This may not be a direct answer, but I hope this example can explain more on why React.ReactNode should not be allowed.
React.ReactNode is broad, and you can assign almost anything to it. For example
const Foo = () => { return 45; };
// no compile time error because even a functions are a valid ReactNode
const Bar: React.ReactNode = Foo
But what happens when we try to render it inside any valid component
At compile time - No error
const FooBar: React.FC<FooProps> = () => {
// again no compile time error because Bar is a valid ReactNode
return <div>{Bar}</div>
}
At runtime - Error Functions are not valid react child

TypeScript not inferring props from React.ComponentType

I have the following function which I want to use to take in a ComponentType and its props in order to allow me to inject those props along with the RouteComponentProps
const routeComponentFactory = <TProps extends {}>(
Component: React.ComponentType<TProps>,
props: TProps
) => {
return (routeProps: RouteComponentProps) => {
return <Component {...routeProps} {...props} />;
};
};
This function works correctly if I explicitly specify TProps, for example:
interface MyComponentProps { a: number; b: number; }
const MyComponent: React.FunctionComponent<MyComponentProps> = () => null;
routeComponentFactory<MyComponentProps>(MyComponent, {});
I get an error there for not providing a and b in the object.
However, if I remove the explicit <MyComponentProps>, the error goes away and I am allowed to call the function with an empty object.
How can I make TypeScript properly infer MyComponentProps?
If you enable strictFunctionTypes you will get an error:
Type 'FunctionComponent<MyComponentProps>' is not assignable to type 'FunctionComponent<{}>'.
There are two possible inference sites for TProps the arguments Component and props both contain the type parameter TProps so typescript tries to find a type that will make both sites happy. Since if TProps were {} both the argument {} and the props type MyComponentProps would be assignable to it typescript infers TProps to be {} in order to keep everyone happy.
The reason that under strictFunctionTypes you do get an error is that by default function type behave bivariantly (so a function (p: MyComponentProps) => JSX.Element is assignable to (p: {}) => JSX.Element). Under strictFunctionTypes function types behave contravariantly so such an assignment is disallowed.
The solution to get an error even without strictFunctionTypes is to decrease the priority of the props inference site, so the compiler picks what is good for Component and checks it against props. This can be done using an intersection with {}:
const routeComponentFactory = <TProps extends {}>(
Component: React.ComponentType<TProps>,
props: TProps & {}
) => {
return (routeProps: any) => {
return <Component {...routeProps} {...props} />;
};
};
interface MyComponentProps { a: number; b: number; }
const MyComponent: React.FunctionComponent<MyComponentProps> = () => null;
routeComponentFactory(MyComponent, {}); // Argument of type '{}' is not assignable to parameter of type 'MyComponentProps'
I cannot see it allowing me to call the function with an empty object but in my case it is complaining that the MyComponent prop is invalid.
It seems the problem is it is inferring the type from the second parameter - I guess because it is matching on the simplest type.
You could define your function like this to force it to infer the correct type:
const routeComponentFactory = <TProps extends {}>(
Component: React.ComponentType<TProps>
) => (
props: TProps
) => {
return (routeProps: RouteComponentProps) => {
return <Component {...routeProps} {...props} />;
};
};
And then call it like this:
routeComponentFactory(MyComponent)({});
And it should complain about the empty object correctly in this case.

Resources