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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With