With React functional components, we can render children like this:
import React from 'react';
const MyComponent = (props: React.PropsWithChildren) => {
return props.children;
}
However, when I go to use this I get an error:
...
return <MyComponent />;
...
or
...
return (
<MyComponent>
<div>hello</div>
</MyComponent>
);
...
leads to
'MyComponent' cannot be used as a JSX component. Its return type 'ReactNode' is not a valid JSX element. Type 'undefined' is not assignable to type 'Element | null'.
One solution is to wrap the functional component's return value in a fragment:
const MyComponent = (props: React.PropsWithChildren) => {
return <>{props.children}</>; // <= useless fragment is here
}
However, then I trigger the eslint: react/jsx-no-useless-fragment
error.
Is there a way use the children such that I don't have either of these issues?
Edit
Here is a playground link
Is there a way to convert from a ReactNode to a valid JSX component other than by using a fragment?
Additional Note: eslint-plugin-react
has an option for allowExpressions
that covers this use case. Essentially it will allow "useless" fragments when the fragment is doing the job of ReactNode
=> JSX element conversion.
CodePudding user response:
There are 2 important points I see here:
The revision you provided in your last code block with the children wrapped in a fragment is important. Children can be an array of elements which would not be directly renderable. It needs to be wrapped in a valid element to cover that case (or checked to ensure it's not an array).
As @crashmstr demonstrated in their comment, children need to be provided to the component for them to be rendered so correctly using that component would look something like this:
<MyComponent>
<p>stuff here</p>
</MyComponent>
That should silence the eslint error.
CodePudding user response:
This is a well known issue with JSX Typescript.
The answer is that the useless fragment is in fact, not useless in Typescript. I would wrap children in a fragment.
The ESLint plugin author even has a note and solve to the rule by enabling allowExpressions
in the rule:
When true single expressions in a fragment will be allowed. This is useful in places like Typescript where string does not satisfy the expected return type of JSX.Element. A common workaround is to wrap the variable holding a string in a fragment and expression.
CodePudding user response:
The "useless" fragment is actually the same as calling
React.createElement(React.Fragment, null, props.children);
React.createElement
is able to use a component type (React.Fragment
) and children to create a JSX element of that type with those children.
So the solution is to do this:
const MyComponent = (props: React.PropsWithChildren) => {
return React.createElement(React.Fragment, null, props.children);
}
That makes TS and eslint both happy. However, JSX is favored over createElement
. Enabling the option {"allowExpressions": true}
for the eslint rule (see question for more details) and using a JSX-style fragment is the best choice here.
Thanks to everyone for the help.