'Converting Flow to TS - 'Component cannot be used as a JSX component' when imported from installed library

I have recently started to convert an older React 15 with Flow types codebase to an up-to-date CRA with TS. I used the flow-to-ts script to convert all the files.

I'm getting a Typescript error all across the codebase when importing a component from an installed package to be used in the render function of a component:

This is it for the Router component imported from the react-router-dom package

'Router' cannot be used as a JSX component.
  Its instance type 'Router' is not a valid JSX element.
    The types returned by 'render()' are incompatible between these types.
      Type 'ReactNode' is not assignable to type 'false | Element | null'.ts(2786)

But you can replace the 'Router' with the name of any component in the project when it is in the render function. I also get it when importing a styled component from another file. Seeing as it is pretty global it makes me think it is a config issue, which is hard to find or being overridden by something to do with the old flow code. Mind you, I have created a whole new project and copied in what I need without every installing Flow in it and I get the same issue. What it feels like to me is the compiler is thinking every Component is a function of some kind.

This post here is what I thought it must be, a conflict between multiple versions of @react/types but when listing them out there was just the root one, an one for testing. I used the resolution as well but it made not difference. But something list this being the cause makes the mose sense to me

Is there some .d.ts settings I am missing, or a way to force the compiler to know that they are valid components?

I also thought it would be something like this and tried wrapping the components in fragments inside my components, but it was still the same, and I can't change all the components in the external libraries to be wrapped in fragments before they are imported.

I have all the type files installed and they are all up to date.

Here's the tsconfig for reference:

{
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "build/dist",
    "module": "esnext",
    "target": "es5",
    "lib": [
      "es6",
      "dom"
    ],
    "sourceMap": true,
    "allowJs": true,
    "jsx": "react-jsx",
    "moduleResolution": "node",
    "rootDir": "src",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "importHelpers": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "strict": true,
    "noFallthroughCasesInSwitch": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false
  },
  "exclude": [
    "node_modules",
    "build",
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest",
    "src/setupTests.ts",
    "src/redux/*",
    "craco.config.ts"
  ],
  "include": [
    "src"
  ]
}

And I have also tried the default CRA tsconfig as well.



Solution 1:[1]

Quick word of caution here, there might not be a time-efficient solution to the problem.

Second complication lies in the fact that the automated tool used was not guaranteed to produce an accurate conversion. It's supposed to go from types that were hinted and inferred to richer strong-typing.

And thirdly, without codebase access it's not possible for us here to validate that solutions proposed will work. But we can try.

  1. Finding a version mix that works better for the specific react-router, react and react-dom versions you're using.

    • npm show @types/react-router versions
  2. If you can upgrade to React v18 safely, meaning that the packages in the project don't specifically rely on crazy internals, or deprecated APIs, it's advisable to do just that: npm i react@18 react-dom@18 @types/react@18 @types/react-dom@18.

    • if on newer NodeJS and peer dependency errors can be safely ignored (means testing the problematic packages manually), we can create an .npmrc file next to package.json and add legacy-peer-deps=false in it.
  3. If you cannot upgrade to v18, the pre-upgrade alternative was to define your own ReactNode like so:

    import { ReactChild, ReactPortal, ReactNodeArray } from 'react';
    
    export type FixedReactFragment =
      | {
          key?: string | number | null;
          ref?: null;
          props?: {
            children?: FixedReactNode;
          };
        }
      | ReactNodeArray;
    export type FixedReactNode =
      | ReactChild
      | FixedReactFragment 
      | ReactPortal
      | boolean
      | null
      | undefined;
    

    Followed changing all code to rely on this return type instead for components.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Silviu-Marian