Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return string or JSX element in TypeScript React component?

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?

like image 987
alien Avatar asked Feb 22 '26 16:02

alien


2 Answers

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.

like image 152
Fez Vrasta Avatar answered Feb 25 '26 04:02

Fez Vrasta


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;

like image 36
ARZMI Imad Avatar answered Feb 25 '26 04:02

ARZMI Imad