Using JS React component in TypeScript: JSX element type 'MyComponent' is not a constructor function for JSX elements
Asked Answered
B

3

11

I'm working with a JavaScript legacy project which uses React framework. We have there some React component defined which I'd like to re-use in a totally different TypeScript React project.

The JS React component is defined in controls.jsx file and looks as follows:

export class MyComponent extends React.Component {
  render() {
    return <h1>Hi from MyComponent! Message provided: {this.props.message}</h1>;
  }
}

In my TypeScript React project I'm trying to use it like that:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { MyComponent } from "../JavaScriptProject/controls";

ReactDOM.render(
  <MyComponent message="some nice message"/>,
    document.getElementById("documents-tree")
);

but I'm getting the following error: enter image description here

Error messages say:

JSX element type 'MyComponent' is not a constructor function for JSX elements. Type 'MyComponent' is missing the following properties from type 'ElementClass': context, setState, forceUpdate, props, and 2 more.ts(2605) JSX element class does not support attributes because it does not have a 'props' property.ts(2607)

I've already tried the solution with custom typings file described in this question, but it changes nothing.

I understand that the JS component has some strongly-typed properties required by TypeScript missing, but in my tsconfig.json I have the allowJs set to true:

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": ".",
    "experimentalDecorators": true,
    "jsx": "react",
    "noEmitOnError": true,
    "outDir": "lib",
    "sourceMap": true,
    "target": "es5",
    "lib": [
      "es2015",
      "dom"
    ]
  },
  "exclude": [
    "node_modules",
    "dist",
    "lib",
    "lib-amd"
  ]
}

so I hoped it should work...

Thanks for your help

Basilisk answered 17/12, 2019 at 8:52 Comment(10)
Did you try these solutions -> github.com/DefinitelyTyped/DefinitelyTyped/issues/21242 ?Guernica
I tried, but nothing helps... I don't use yarn so few of them were irrelevant. The others don't help at all...Haddix
It works fine for me without any issue. i just created a typescript app and added the controls.jsx file at src.Genip
Hmm... so maybe it's a matter of some npm packages? Or their versions? Could you share your tsconfig.json, package.json and maybe webpack.config.js?Haddix
this sounds a lot like you have mismatched react and react-dom typings. can you share your package-lock & package.json? you may need to place package-lock on pastebin/gist due to its length.Atherton
Sure - package.json: pastebin.com/Eq0YPsCN, package-lock.json: pastebin.com/0ffN2PzFHaddix
Hmm. Do you only get this error in your editor? Is it still present when you compile using the command line?Atherton
how about set compiler option allowSyntheticDefaultImports to true.Mattins
@DanPantry I'm getting the error also when compiling using the command line.Haddix
@RollingPanda it doesn't help at allHaddix
Y
2

You should turn allowSyntheticDefaultImports to true since React does not export default.

(source: https://github.com/facebook/react/blob/master/packages/react/index.js)

{
  "compilerOptions": {
    "allowJs": true,
    "baseUrl": ".",
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true, // turn on allowSyntheticDefaultImports
    "jsx": "react",
    "noEmitOnError": true,
    "outDir": "lib",
    "sourceMap": true,
    "target": "es5",
    "lib": [
      "es2015",
      "dom"
    ]
  },
  "exclude": [
    "node_modules",
    "dist",
    "lib",
    "lib-amd"
  ]
}

Also, you should add the shape for component props. For example, in your code:

export class MyComponent extends React.Component<{ message: string }> { ...
Yakutsk answered 25/12, 2019 at 3:30 Comment(1)
Thank for answering. Setting allowSyntheticDefaultImports to true changes nothing, I'm still getting the same error. I can't add shape for components props, because it's defined in a .jsx file and I'm getting the following error: "'type arguments' can only be used in a .ts file.ts(8011)"Haddix
G
0

Here are the configs for you reference which i am using in my sample application.

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react"
  },
  "include": [
    "src"
  ]
}

package.json

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.4.0",
    "@testing-library/user-event": "^7.1.2",
    "@types/jest": "^24.0.23",
    "@types/node": "^12.12.18",
    "@types/react": "^16.9.16",
    "@types/react-dom": "^16.9.4",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-scripts": "3.3.0",
    "typescript": "^3.7.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

I have not used webpack.

Genip answered 17/12, 2019 at 16:15 Comment(1)
Even if I take your configs it doesn't help... :/Haddix
G
0

The way I see it, you have two options:

  1. You can supply a declarations file in the same directory as controls.jsx named controls.t.ds.

This options has two ways of going about it. The easiest is just to export a function with the name of MyComponent:

export function MyComponent(): any;

The second is to export a variable that mimics the class structure of a component:

interface Element {
    render(): any;
    context: any;
    setState: any;
    forceUpdate: any;
    props: any;
    state: any;
    refs: any;
}

interface MyComponent extends Element {}

export var MyComponent: {
    new(): MyComponent;
}
  1. Your second option is to import your component and re-export it with a type definition. This file would be included somewhere within project:
import React from 'react';
import {MyComponent as _MyComponent} from '../project-a/Controls';

interface MyComponent extends React.Component {}

const MyComponent = (_MyComponent as any) as {
    new(): MyComponent;
};

export {MyComponent};

And then in your file that wants to use MyComponent, you would import it from that new file instead.

These are all really gross solutions, but I believe it's the only solution as TypeScript doesn't let you define a module that is relative to your project:

declare module "../project/Controls" {

}

Hope this helps.

Gastrin answered 24/12, 2019 at 21:8 Comment(5)
I tried the option 2 and I declared this extra file, additionally adding a message of string type to the component (MyComponent extends React.Component<{ message: string }> and VS Code doesn't give me any errors when importing this component from the new file. However, I'm still getting a compile error in the new file: Module not found: Error: Can't resolve '../../../../JS_TS_Project/Scripts/application/controls'Haddix
@DawidSibiński What packager are you using? Metro, babel, WebPack?Gastrin
I'm using webpackHaddix
@DawidSibiński I believe you'll have to tell Webpack to include the JS_TS_Project in it's include resolution. See this SO answer: https://mcmap.net/q/1159583/-how-to-import-an-external-file-from-project-root-with-webpackGastrin
Thanks @Kyle, but still can't get it to work... I gave up a bit, cause finally, I used a separate TS component. However, the issue still remains open and interesting. Even if one of these solutions worked, this is still tight-coupling both modules/projects and far from a reliable and re-usable solution.Haddix

© 2022 - 2024 — McMap. All rights reserved.