13

I am getting this weird TypeScript error:

import React from 'react'

type Props = {
  children: string
}

const Container = (props: Props) => {
  const isNew = true // make an api call...

  if (isNew) {
    return <NewContainer {...props} />
  } else {
    return <OldContainer {...props} />
  }
}

const NewContainer = ({ children }: Props) => {
  const isSpecial = useIsSpecial()

  if (!children) {
    return null
  }

  if (!isSpecial) {
    return children
  }

  return <a>{children}</a>
}

const OldContainer = ({ children }: Props) => {
  const isSpecial = useIsSpecial()

  if (!children) {
    return null
  }

  if (!isSpecial) {
    return children
  }

  return <a>{children}</a>
}

Those get used like this:

<Container>foo</Children>

I then get these typescript error:

'NewContainer' cannot be used as a JSX component.
  Its return type 'string | Element' is not a valid JSX element.
    Type 'string' is not assignable to type 'Element'.
'OldContainer' cannot be used as a JSX component.
  Its return type 'string | Element' is not a valid JSX element.
    Type 'string' is not assignable to type 'Element'.

If I remove the if (!isSpecial) return children, and change it to if (!isSpecial) return <span>{children}</span>, it works fine. Why won't it allow me to return a string? How do I fix this in TypeScript?

3 Answers 3

14

TypeScript's React types are incorrect and disallow string as a return value of a component type.

A React component is allowed to return the following types (a React Node):

  • null
  • boolean
  • number
  • string
  • React Element
  • React Portal
  • Array of (React Node or undefined)

You can easily test this running the snippet below and observing the component is correctly rendered.

function HelloWorld() { return "Hello, World!"; }
ReactDOM.render(React.createElement(HelloWorld), document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id="root"></div>

You can also notice that running ReactDOM.render("Hello, World!", ...) works as well, since the render function of React DOM expects a valid React Node, and string is such.

But you can also use as reference the isNode function of the prop-types package or the Flow's React$Node type definition, to verify what a React Node type accepts, both type definitions have been created by Facebook Meta itself.

TypeScript maintainers don't seem to be interested in fixing this issue, along with the incorrect ReactNode type that wrongly accepts undefined, so you can only resort to workarounds such as wrapping the text into a Fragment, as explained in other comments.

Sign up to request clarification or add additional context in comments.

1 Comment

Can you link to where "TypeScript maintainers don't seem to be interested in fixing this issue"?
3

In your case children is a string so you should wrap it with React.Fragment or <></> but if children was element like below it won't throw an error

<Container><p>foo</p></Container>

so just add react fragment

import React from "react";

const Container = (props) => {
  const isNew = true; // make an api call...

  if (isNew) {
    return <NewContainer {...props} />;
  } else {
    return <OldContainer {...props} />;
  }
};

const NewContainer = ({ children }) => {
  const isSpecial = true;

  if (!children) {
    return null;
  }

  if (!isSpecial) {
    return <React.Fragment>{children}</React.Fragment>;
        // or return <>{children}</>;
  }

  return <a>{children}</a>;
};

const OldContainer = ({ children }) => {
  const isSpecial = useIsSpecial();

  if (!children) {
    return null;
  }

  if (!isSpecial) {
    return <React.Fragment>{children}</React.Fragment>;
    // or return <>{children}</>;
  }

  return <a>{children}</a>;
};
const Main = () => (
  <Container>
    <a>foo</a>
  </Container>
);

export default Main;

Comments

2

To please TypeScript where it complains, indicate its desired type explicitly with

return children as JSX.Element;

or for plain string

return "MyTextToRender" as unknown as JSX.Element;

to prevent

Conversion of type 'string' to type 'Element' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. ts(2352)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.