Home > Net >  How do you convert `props.children` to a JSX element?
How do you convert `props.children` to a JSX element?

Time:08-09

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:

  1. 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).

  2. 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:

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-useless-fragment.md#allowexpressions

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.

  • Related