Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 219
Vencovsky Avatar asked Oct 15 '22 02:10

Vencovsky


1 Answers

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

like image 74
subashMahapatra Avatar answered Nov 16 '22 03:11

subashMahapatra