TypeScript Version: 2.1.1 / nightly (2.2.0-dev.201xxxxx)
Code
// a.tsx
import React from "react";
export class SomeComponent extends React.Component<{}, {}> {
public render() {
return <div><span>Hi!</span></div>;
}
}
// b.tsx
import { SomeComponent } from "./a";
let x = <SomeComponent />; // error line
Expected behavior:
No errors - React
refers to the React
imported in a.tsx
, not a global.
Actual behavior:
error TS2686: 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead
Error is on the line marked // error line
above.
The error can be subdued by adding any of the following in b.tsx
.
declare var React: any;
var React;
import React from "react";
import * as React from "react";
I assume that you're getting the error in b.tsx
, in which case I suspect the problem is that you forgot to import React in b.tsx
. Can you verify and see if that fixes it?
@DanielRosenwasser As I mentioned at the end, that does stop the error - but I don't think it's correct.
b.tsx
doesn't directly rely on React - a.tsx
relies on React and b.tsx
relies on a.tsx
. Why should b.tsx
have to import both a.tsx
and React?
b.tsx
does directly rely on React. The <SomeComponent />
line will be turned into a React.createComponent
call.
Okay, I understand what's going on here now @RyanCavanaugh. It feels very clunky to me though, as I have to import React in my source code when my source code itself doesn't use the React
variable. I'm not sure of any potential solutions for it though.
@RyanCavanaugh
Working as intended but still open? Why not close the issue?
As the OP asserted, it's an odd one; tslint complains about an unused import.
Related:
https://github.com/SharePoint/sp-dev-docs/issues/27
People complain when we close issues so we try to let them close for themselves.
Hm @RyanCavanaugh, why would it not possible for tsc to emit the appropriate imports for the statements it generates? At least if the proper target/module/moduleResolution combination was used?
I find it to be very counter-intuitive to have to use an unused import in my tsx files to make it work...
@RyanCavanaugh I think leaving issues open for a while is great because it implies a high level of comfort with _having the discussion_.
Some open source projects have a very different philosophy where they close issues almost immediately. Heck, some even have a "Can Be Closed?" label that they use when multiple team members are supposed to take a look before closing occurs. Seeing that label on an issue is like a slap in the face.
TLDR: keep it up :heart:
I've repeatedly run up against this issue while working on a ReactXP app. I understand the working as intended
tag from the perspective of someone who A) works on the typescript compiler, and B) knows the internals of the library being extended (React
in this case).
But from the perspective of a user, this feels like it breaks encapsulation, one of the intended features / goals of Typescript. For me, the expected behaviour would be that the compiler walks up the chain of imports in order to find the required (namespaces, methods, etc. I'm sure I'm off on the lingo at this point, but I expect that you follow my drift).
What dire consequences do we foresee if the compiler did walk up the chain of imports in a file in order to find what it was missing?
I am fairly new to react, typescript and the whole js/web world, (since I've been traditionally more of a embedded C/C++, native and .NET developer) but I've been using JS & TS quite a bit in recent days, and I believe I have a something to say to people on both sides of this issue. And some suggestions for a solution.
For the OP, and others that agree with him, you have to note that TypeScript isn't a mature, fully-feature,d self-contained language, and neither is React. They both basically are constrained by the current limitation that they must be transpiled into something that is valid javascript. (And not a modern, elegant version, but ES5 or AMD, or something even older.)
For the developers of TypeScript, you have to realize that we users don't want to be beleaguered by these constraints and want to use a language that feels like it's its own language, unhindered by anything but its own concepts.
The users expect there should be some way that the import call causes the React namespace/variable name to be carried along with it implicitly enough to allow the creation of imported components, but not in pollution of the namespace.
One way this could be done is to implicitly provide the React type as an automatic template parameter of the Component class. Am I correct in assuming there will never (or at least should never) be two different versions of this React type in the same program? If so, something like this might work:
// react/Component.js module
import React from 'react';
...
class React.Component <T1,T2,T3=React>{
React: T3;
// or whatever syntax will provide the imported type as a static typedef member of this class
// in C++ it would be:
public: typedef T3 React;
// or if you could import from inside the class, you wouldn't need to change the templates at all:
import React from 'react'
... // rest of implementation
}
// a.tsx
import React from "react";
export class SomeComponent extends React.Component<{}, {}> {
public render() {
return <div><span>Hi!</span></div>;
}
}
// b.tsx
import { SomeComponent } from "./a";
let x = <SomeComponent />;
where <SomeComponent />
is implicitly transpiled into an implementaition where this is created:
let x = SomeComponent.prototype.React.createComponent(SomeComponent.prototype, ...)
instead of this:
let x = React.createComponent(SomeComponent.prototype, ...)
Such a solution sounds like it could be very doable, especially as long as you can safely assume that SomeComponent.React will always reference the same React as an import React from "react"
would.
Another solution might be to improve React.Component construction so that the transpiler provides all the references to all the type information it needs to be constructed without additional imports from the file where it's instantiated.
Another way this could feasibly work is with namespaces, since you can't import directly into classes, but you can import into a local namespace that should provide a closure where the import React from "react"
type is known. If it doesn't then that would seem to be a bug in TypeScript...
// react/Component.js module
import React0 from 'react';
...
namespace privateReactComponentNamespace {
import React = React0;
export default class React.Component <T1,T2>{
static React: React = React;
// or whatever syntax will provide the imported type as a static typedef member of this class
... // rest of implementation
}
}
export default privateReactComponentNamespace;
In any case, I would side with the TypeScript team that the behavior noted by the OP, while odd, is a result of TS doing what it was written to do. Furthermore, I think the bug this time, is really in React's code, in that it fails to carry along the dependency type information it needs to be created when another module imports it.
I had this error with React, and found that a simple change in the case of my import fixed the issue.
Original import
import * as react from 'react'
When I updated it to this it was fixed
import * as React from 'react'
Weird behaviour or is this the way it's meant to be used?
I had to add React, from this
import { StyleSheet, TouchableOpacity, Text } from 'react-native'
to this
import React, { StyleSheet, TouchableOpacity, Text } from 'react-native'
Solution provided by @EvanBurbidge
import * as React from 'react'
works, thanks man!
Most helpful comment
I assume that you're getting the error in
b.tsx
, in which case I suspect the problem is that you forgot to import React inb.tsx
. Can you verify and see if that fixes it?