3

I'd like to have a variable that can be either a React component or a string, like this:

function MyComponent(): JSX.Element {
  let icon: JSX.Element | string = "/example.png";  // (simplified)

  return <div>{typeof icon === "JSX.Element" ? icon : <img src={icon} />}</div>;
}

however TS complains about my condition saying:

TS2367: This condition will always return 'false' since the types '"string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"' and '"JSX.Element"' have no overlap.

What should I do?

Garrett
  • 4,007
  • 2
  • 41
  • 59
  • 1
    Why not ask if typeof is "string" instead? – Camilo Nov 23 '22 at 20:22
  • @Camilo you're right, that trick would work in this simplified case, but in the full case, I'm using `let icon: JSX.Element | StaticImageData` and Next.js's `Image` component, and it seems not to work there. Besides, it seems like you should be able to type narrow `JSX.Element` out of the type. – Garrett Nov 23 '22 at 20:34
  • You may get a better response showing your full custom type example, because it's possible we could modify that type to help you narrow – wrhall Nov 23 '22 at 20:38

2 Answers2

6

The typeof operator cannot return "JSX.Element". It can only return the string types listed in that error message:

"string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"

Which means that you will need to check against something in that list, like "string".

function MyComponent(): JSX.Element {
  let icon: JSX.Element | string = "/example.png";
  
  return <div>{typeof icon === "string" ? <img src={icon} /> : icon }</div>;
}
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
1

The accepted answer is the best for my simplified question.

If for some reason, you do want to type narrow against the element, rather than against string, apparently you can also use React.isValidElement, which type narrows.

You'd have to change the type JSX.Element to ReactElement:

function MyComponent(): JSX.Element {
  let icon: ReactElement | string = "/example.png";  // (simplified)

  return <div>{React.isValidElement(icon) ? icon : <img src={icon} />}</div>;
}

If changing to JSX.Element is unacceptable, you could create your own user-defined type guard, which interrogates the object to determine if it is indeed a JSX.Element (see here).

Garrett
  • 4,007
  • 2
  • 41
  • 59