Definitelytyped: React components should return ReactNode, or children should be ReactElement

Created on 14 Jul 2017  ·  18Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

  • [x] I tried using the @types/react package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @ericanderson @onigoetz @tkrotoff @digiguru @morcerf @johnnyreilly @bbenezech @pzavolinsky @DovydasNavickas

The following components

const SFC: React.StatelessComponent = (props) => props.children;

class C extends React.Component {
  render() {
    return this.props.children;
  }
}

give the errors

Type '(props: { children?: ReactNode; }) => ReactNode' is not assignable to type 'StatelessComponent<{}>'.
  Type 'ReactNode' is not assignable to type 'ReactElement<any> | null'.
    Type 'undefined' is not assignable to type 'ReactElement<any> | null'.

Class 'C' incorrectly extends base class 'Component<{}, {}>'.
  Types of property 'render' are incompatible.
    Type '() => ReactNode' is not assignable to type '() => false | Element | null'.
      Type 'ReactNode' is not assignable to type 'false | Element | null'.
        Type 'undefined' is not assignable to type 'false | Element | null'.

It works if you wrap children in an Element.

Returning children is quite common when making Provider components that just add something to context.

Most helpful comment

I am currently wrapping children in React.Fragment to get around this.

All 18 comments

I tried the following fixes to index.d.ts

-        render(): JSX.Element | null | false;
+        render(): ReactNode;

-        (props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
+        (props: P & { children?: ReactNode }, context?: any): ReactNode;

but I got errors all over the place when trying to lint that I'm not sure how to fix.

TypeScript compile error: JSX element type 'ReactNode' is not a constructor function for JSX elements.

Nope, this change is incorrect. You cannot return multiple elements in React currently. Even if you do it somewhere, most probably it it is encapsulated in a single one somehow.
Only in React 16 with Fiber this might be possible :)

not sure if they changed react behavior, but the redux / react author showcased the ability to return this.props.children for creating thin wrappers (only to add childContext to elements), as seen in https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context

react-native does allow you to return an array of JSX elements from render, though. an work around is to wrap the this.props.children with React.Children.only(this.props.children) and it'll throw if there's more than a single element as root

Right, you can use React.Children.only but both ReactNode AND ReactElement are valid values of this.props.children.

const Title = props => <h1>{props.children}</h1>;
<Title>Woah</Title>

is valid and reasonable React code.

You can return an array of elements in react 16, but TS throws an error because it thinks you cant.

Edit: do this children: JSX.Element[]; to fix it

From my experience so far, it seems ReactNode should be valid.

const Id: React.SFC<{}> = (props) => props.children

const App = ()= > <Id>This should work but instead fails</Id>

Is there any chance this will be reflected in @types/react?

Without this I am unable to type React children

I am currently wrapping children in React.Fragment to get around this.

Any updates on it?

Not sure if this is any more or less correct than wrapping children in a React.Fragment, but I have had luck with doing

return (children as React.ReactElement);

return <>{foo(}</>; all night long. <> is a workaround.

This is causing issues for me as well.

TL;DR:

I have an HOC that adds props to a given component, and I am trying to use it with a functional component that has return children;. It only works if I use the workaround return <>{children}</>;.

More details:

Here is a simplified version of the HOC:

export interface ExtraProps {
  extraProp1: string;
}

export const withExtraProps = <P extends object>(Component: React.ComponentType<P>) => {
  return class extends React.Component<Omit<P, keyof ExtraProps>> {
    render() {
      const extraProps = {
        extraProp1: 'test'
      };

      return (
        <Component {...this.props as P} {...extraProps} />
      )
    }
  }
}

Here is a simplified version of the functional component:

interface ComponentProps extends ExtraProps {
  children?: React.ReactNode;
  loadingComponent?: ReactElement;
}

export function ExampleComponent(props: ComponentProps) {
  const { children, loadingComponent } = props;

  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      await someAsyncCall();
      setLoading(false);
    }

    fetchData();
  });

  if (loading) {
    return loadingComponent || null;
  }

  return children;
}

// This line errors:
export const FeatureFlag = withExtraProps(ExampleComponent);

I get the following error:

Argument of type '(props: ComponentProps) => {} | null | undefined' is not assignable to parameter of type 'ComponentType<ComponentProps>'.
  Type '(props: ComponentProps) => {} | null | undefined' is not assignable to type 'FunctionComponent<ComponentProps>'.
    Type '{} | null | undefined' is not assignable to type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)> | null'.
      Type 'undefined' is not assignable to type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)> | null'.

If I add a return type of ReactElement | null to the ExampleComponent, it stops complaining about passing it to the HOC, but still errors where I am returning the children:

export function ExampleComponent(props: ComponentProps): ReactElement | null {
  ...
  ...
  return children; // Type 'ReactNode' is not assignable to type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)> | null'.

}

As with the others here, if I change it to return <>{children}</>;, it works, but feels gross and unnecessary.

I have the _exact_ same issue as @kyrstenkelly - An HOC function being passed a React.FC which renders it's children.

The <> children(...) </> fix works, but is there a better solution yet?

Also having this problem! Currently doing the suggest solution of wrapping solution with React.Fragment

Same here

interface IIntlMessageProps {
    id: string;
    valuesGiven?: {[key:string]:string};
}

// this doesn't work - -- TS2769 Type 'null' is not assignable to type '(nodes:ReactNodeARray) => ReactNode' | ... | undefined
export const IntlMessage: React.FC<IIntlMessageProps> = (props) => {
    return (
        <FormattedMessage id={props.id} values={props.valuesGiven}>
            {props.children}
        </FormattedMessage>
    )
};
//Nope -- TS2769 Type 'Element' is not assignable to type '(nodes:ReactNodeARray) => ReactNode'
export const IntlMessage: React.FC<IIntlMessageProps> = (props) => {
    return (
        <FormattedMessage id={props.id} values={props.valuesGiven}>
            <React.Fragment>{props.children}</React.Fragment>
        </FormattedMessage>
    )
};

// Nope -- TS2769 Type 'Element' is not assignable to type '(nodes:ReactNodeARray) => ReactNode'
export const IntlMessage: React.FC<IIntlMessageProps> | null = (props) => {
    return (
        <FormattedMessage id={props.id} values={props.valuesGiven}>
            <>{props.children}</>
        </FormattedMessage>
    )
};

// Nope -- TS2769 Type '{}' is not assignable to type '(nodes:ReactNodeARray) => ReactNode'
export const IntlMessage: React.FC<IIntlMessageProps> = (props) => {
    return (
        <FormattedMessage id={props.id} values={props.valuesGiven}>
            {props.children ? props.children : undefined}
        </FormattedMessage>
    )
};


I believe I have tried everything that was adviced here... :( Any clue ?

The solution is you need to wrap it in fragment.

import React from 'react'

const Something: React.FC = ({ children }) => (
 <> { children } </>
)

this will fix the Type '({ children }: { children?: ReactNode; }) => ReactNode' is not assignable to type 'FunctionComponent error.

If possible, I'd like to avoid fragments and make the JSX type system support the same kinds of constructs that JavaScript and Flow React supports, which helps TypeScript work better with 3rd party library patterns.

Another non-ideal I find is that fragments deepen the component tree, especially in utility/wrapper components (case-in-point: react-placeholder). Casting to a React.ReactElement seems like a better approach in these kind of libraries, but I'd rather not need to cast.

I tried an approach to change that type, but ran stuck with JSX typechecking using a functional component of the new type. I still have the branch, could have sworn I made a PR asking for help, can't find it back...

Okay, if type needs to disallow arrays of nodes it does not mean that it should disallow ReactText, ReactPortal, boolean, null, and undefined as well what usage of ReactElement does.

What the rational of prohibiting these additional types?

Was this page helpful?
0 / 5 - 0 ratings