Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TS files cannot use JSX components declared as a classes in JS/JSX file #14558

Closed
DanielRosenwasser opened this issue Mar 9, 2017 · 38 comments
Closed
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Mar 9, 2017

CC @donataswix

It appears that using a React component written as a class in a .js file causes some issues.

Start a project with the following files:

tsconfig.json

{
  "compilerOptions": {
    "module": "es2015",
    "target": "esnext",
    "jsx": "react-native",
    "strictNullChecks": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "lib": [
      "es5",
      "es6",
      "es7",
      "es2017",
      "dom"
    ],
    "outDir": "dist"
  },
  "include": [
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

index.tsx

import React from 'react';
import Hello from './hello';

class A extends React.Component<void,void> {
  render() {
    return (
      <div>
        <Hello/>
      </div>
    );
  }
}

hello.js

import React from 'react';

export default class Hello extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

Expected: No problems
Actual: Error on <Hello />: JSX element type 'Hello' does not have any construct or call signatures.

Note that if you add type arguments to Component in hello.js, this will actually fix the problem (while creating new ones).

Potentially related is #13609.

@DanielRosenwasser DanielRosenwasser added Bug A bug in TypeScript Salsa labels Mar 9, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Mar 9, 2017

Can you give #14396 a try and see if it fixes the issue?

@timothyallan
Copy link

This is killing me trying to migrate a large project to Typescript. I've got a hundred plain ol React components and I'd love to be able to do 1 at a time, but once converted to .tsx, they can't import any other .js files without this error.

@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Apr 19, 2017
@mhegazy mhegazy added this to the TypeScript 2.3.1 milestone Apr 19, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Apr 19, 2017

This is addressed by #14907. @timothyallan please give [email protected] a try and let us know if you are running into other issues.

@mhegazy mhegazy closed this as completed Apr 19, 2017
@robin-anil
Copy link

This is not solved with [email protected] I am still seeing issues

@mhegazy
Copy link
Contributor

mhegazy commented May 5, 2017

I see it working on latest. if you are seeing issues please file a new issue:

c:\test\14558>type tsconfig.json
{
  "compilerOptions": {
    "module": "es2015",
    "target": "esnext",
    "jsx": "react-native",
    "strictNullChecks": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "checkJs": true,
    "lib": [
      "es5",
      "es6",
      "es7",
      "es2017",
      "dom"
    ],
    "outDir": "dist"
  },
  "include": [
    "**/*.ts",
    "**/*.tsx",
    "**/*.js"
  ],
  "exclude": [
    "dist",
    "node_modules"
  ]
}
c:\test\14558>type index.tsx
import React from 'react';
import Hello from './hello';

class A extends React.Component<void, void> {
  render() {
    return (
      <div>
        <Hello>
          <div></div>
        </Hello>
      </div>
    );
  }
}
c:\test\14558>type hello.js
// @ts-check

import React from 'react';

export default class Hello extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

c:\test\14558>tsc --v
Version 2.3.2

c:\test\14558>tsc --p tsconfig.json

c:\test\14558>echo %ERRORLEVEL%
0

@robin-anil
Copy link

does it work if you rename hello.js to hello.jsx ?

@mhegazy
Copy link
Contributor

mhegazy commented May 5, 2017

c:\test\14558>move hello.js hello.jsx
        1 file(s) moved.

c:\test\14558>
tsc

c:\test\14558>echo %ERRORLEVEL%
0

@gagarski
Copy link

@mhegazy

Did you try adding properties to Hello?
It seems like Typescript infers that properties of Hello are of type {}. So you cannot render it from TypeScript as <Hello baz="world"/>.

Here is the compiler output:

error TS2339: Property 'baz' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Hello> & Readonly<{ children?: ReactNode; }> & Readonly<{}>'

It can be worked around with React.createElement:

React.createElement(Hello, {baz: "world"} as any)

or by creating temporary variable with properties but this is quite ugly workaround. Can this be done better?

@gagarski
Copy link

https://github.com/gagarski/tsx-jsx - complete minimalistic example

@artemyarulin
Copy link

Can we please reopen it? It doesn't work if component have any props inside and it's a show stopper for React projects which are migrating to TypeScript

@paul-sachs
Copy link

@mhegazy this issue has not been resolved. Is there anyone looking at this?

@eyanez1230
Copy link

Agree, it's not resolved. I too am facing the same issue and would like a resolution.

@dmydry
Copy link

dmydry commented Sep 11, 2017

+1 I have the same problem guys

@mhegazy
Copy link
Contributor

mhegazy commented Sep 11, 2017

The original issue was fixed. but then a change was made to the react.d.ts to make so the default props is {}, i.e. in a .js file all JSX component have no properties.

this is the same issue reported in #18134..

the fix is to change the react declaration file to be P = object instead of P = {} to allow for accessing props.

@gagarski
Copy link

@mhegazy

I tried to monkey-patch @types/react in my example here (npm i and then just editing node_modules/@types/react/index.d.ts)

Here is patched React.Component definition:

interface Component<P = object, S = object> extends ComponentLifecycle<P, S> { }

But I still get a compilation error for this example:

index.tsx(8,16): error TS2339: Property 'baz' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Hello> & Readonly<{ children?: ReactNode; }> & object'.

However, when I am using /** @augments {React.Component<object, object>} */ on JSX class, everything works fine (without monkey-patching).

@mhegazy
Copy link
Contributor

mhegazy commented Sep 11, 2017

you are right. we need to remove the default or make it any. and in both cases, seems a break.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 11, 2017

@weswigham can you take a look here. unfortunately we can not remove the defaults from react definition file as that would be a big breaking change, but what can we do not make sure this scenario still works.

@gagarski
Copy link

Yeah, removing the default would broke a lot of code and any is seems wrong here (component with no state specified should be stateless, not have any state, the same logic should be applied for props).

@z0d14c
Copy link

z0d14c commented Sep 26, 2017

sadly also suffering from this issue -- is a huge deal when trying to migrate incrementally to typescript.

@gpeal
Copy link

gpeal commented Oct 1, 2017

@mhegazy any update here? I'm in a similar boat to @z0d14c in which this makes adopting ts in a large codebase very difficult.

@Phaze1D
Copy link

Phaze1D commented Oct 2, 2017

This worked for me
import * as React from 'react' instead of import React from 'react'

@third774
Copy link

third774 commented Oct 4, 2017

I'm bumping into this as well. For now, thinking the only sane way to migrate to Typescript is to start with components that don't import any other jsx components and work my way to the top.

@weswigham
Copy link
Member

So we know the root cause of this issue - React's type definitions have their default Props and State types set to {} - the intention being that this gives you safety; if you specify the generics, then you can access props and state safely, otherwise you get no props or state (other than what you may inherit from intrinsics). The downside to this is that in JS, you often don't or can't specify the generic arguments - so they default to {} (whereas previous they defaulted to any), and you can't do anything with the props or state without an error. In short, React's types used to default open, but now default closed. This behavior is arguably better for TS users, but is without a doubt a showstopper for JS users relying gradual typing with the same types. We're trying to come up with a solution that can satisfy both user-bases, but it's difficult.

While we work on it, a workaround in the meantime is editing the react.d.ts to use any for its' default props and state types (the replacement would be this: interface Component<P = any, S = any> extends ComponentLifecycle<P, S> { }), or adding JSDoc arguments to your JS components to specify their props and/or state types (which you should consider preferred if it is easy, as when you migrate the file to TS we provide a bunch of quick fixes to translate that JSDoc into proper TS code), for example:

import React, { Component } from "react";

/**
 * @augments {Component<{location: string}, *>}
 */
class MyComponent extends Component {
    render() {
        return <h1>Hello World from {this.props.location}</h1>
    }
}

const x = <MyComponent location="Redmond" />

nkanderson added a commit to nkanderson/rustw-frontend that referenced this issue Oct 8, 2017
@robaweb
Copy link

robaweb commented Nov 12, 2017

Helps for me

//this is tsx file

import React, { Component } from "react";
import SomeComponent from './SomeComponent.js';

/* tslint:disable */
const JsSomeComponent: any = SomeComponent; //todo delete me after refactoring to TS
/* tslint:enable */

class MyTSXComponent extends Component {
    public render() {
        return <JsSomeComponent/>;
    }
}

@mhegazy
Copy link
Contributor

mhegazy commented Nov 13, 2017

We have a fix on the compiler side in #19977 to accommodate the react changes. Can you give tomorrow's typescript@next a try?

@khovansky-al
Copy link

Tempted to try new fix. For the time being or for everyone stuck on old versions in future:

  • use @augments as seen in this thread for stateful components. Like this:
/**
 * @augments {React.Component<*, *>}
 */
  • use @return for stateless components and return "any". I tried returning React.SFC<*> but that didn't work. No idea what I'm doing. Returning any works fine. Like this:
/**
 * @return {*}
 */

@vkrol
Copy link

vkrol commented Nov 22, 2017

@mhegazy this fix does not work if noImplicitAny compiler flag is enabled :(

@mhegazy
Copy link
Contributor

mhegazy commented Nov 22, 2017

Well.. it is an implicit any... if you want the strict no-anys-implicitly-added-by-the-compiler, then use `/** @Augments */ to specify the type.

@vkrol
Copy link

vkrol commented Nov 22, 2017

@mhegazy so in noImplicitAny-mode /** @augments ... */ is the only solution and it will not be changed in the future?

@gpeal
Copy link

gpeal commented Nov 22, 2017

@mhegazy This makes it very difficult to incrementally adopt TS in a large codebase because we have hundreds/thousands of JS components that will not be converted for the foreseeable future.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 22, 2017

but it is an implicit any. you can opt-out of this using --noImplicitAny false

@mhegazy
Copy link
Contributor

mhegazy commented Nov 22, 2017

so i miss understood the @vkrol's comment. I thought he meant getting an error under --noImpclitAny --checkJs which is expected.. but looking at the code again, it is disabled for --noImplcitAny, and that is a bug and should be fixed. apologies for the miss understanding.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 22, 2017

#20232 should fix that.

@gerges-zz
Copy link

Just ran into this as well, just so I'm understanding, with #20232 this fix should work for an incrementally converted codebase when using a non TS React component inside of a TSX component with noImplicitAny enabled

@vkrol
Copy link

vkrol commented Dec 3, 2017

@mhegazy @weswigham it is works in the latest nightly build. Thanks!

@danantal
Copy link

danantal commented Jan 17, 2018

I ran into this as well.. while using react-select with latest typings. Has this issue been fixed in TS 2.6.2?

@ssunday
Copy link

ssunday commented Feb 6, 2018

@danantal Running into this as well. Not on TS 2.6.2, though. I would upgrade, but it's a massive jump for the codebase.

nrc pushed a commit to rust-dev-tools/cargo-src that referenced this issue Mar 2, 2018
Quist pushed a commit to navikt/modiapersonoversikt that referenced this issue Mar 7, 2018
Det er dratt inn fra nav-frontend-moduler. Per nå så opplever jeg litt
krøll med komponenten, sannsynligvis fordi den ikke er skrevet i React.
InteliJ rapporterer at 'EkspanderbartpanelBase does not have any
construct or call signature'. Det funker fint å kompilere og kjøre, men skriptet
feiler ved hot-reload av visittkort-komponenten.

Det kan være relatert til denne:
microsoft/TypeScript#14558. Denne har blitt
merget inn i master, men er ikke ute i en release enda.

Det eksisterer også en PR i nav-frontend på å skrive om panelet til
typecript som forhåpentligvis vil løse problemet:
navikt/aksel#233
@devharf2310
Copy link

devharf2310 commented May 2, 2018

This works for me:

//this is tsx file

import React, { Component } from "react";

const SomeComponent = require('./SomeComponent.js');

/* tslint:disable */
const JsSomeComponent: any = SomeComponent; //todo delete me after refactoring to TS
/* tslint:enable */

class MyTSXComponent extends Component {
    public render() {
        return <JsSomeComponent/>;
    }
}

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests