TypeScript Version: 2.1.5
_This issue was originally reported to React Dev Tools and Dan Abramov referred me to log an issue here._
Code
const ArrowFunction = () => { }; // OK
function NamedFunction() { } // OK
export const ExportedArrowFunction = () => { }; // Problem: shows <Unknown> in react-dev-tools
export function ExportedNamedFunction() { } // OK
const ExportedArrowFunctionOnObject = () => { }; // OK
export { ExportedArrowFunctionOnObject } // OK
Expected behavior:
Using Chrome's React Dev Tools and using each function above as a React functional stateless component, I expected to see each function name in the tools, ex <ArrowFunction> and <ExportedArrowFunction>.
Actual behavior:
As mentioned in code comments in the above example, all function forms show their name in tools as expected except ExportedArrowFunction, which shows as <Unknown>. (And unfortunately, this has become the most common form in my code as of late!) This is apparently because it does not have a function.name. It seems that the export throws off the browser's ability to derive a name from the function. Looking at the emitted ES5 (using AMD modules? I'm actually using CommonJS modules but it looks very similar) I'm guessing that the assignment exports.ExportedArrowFunction = function () { } is what throws browsers off, in that it doesn't pick up ExportedArrowFunction as the name.
Solution?
In the original issue thread it was advised by Dan Abramov that a .name should be emitted by the compiler (if I understood his comment). However it looks like TS does not emit a .name for any function. When I used to write ES5 (many years ago -- thank-you TS! :) ) I would always give my functions names, even when I assigned them to things, like exports.ExportedArrowFunction = function ExportedArrowFunction() {} so that the name would appear in call-stacks. Maybe this could work here? Is there another way we could have exported const functions get a proper name?
This is actually part of the ES spec, but we just haven't implemented it.
We could fix this, but one has to be careful in the following case:
export var foo = 10;
var x = {
foo: () => foo
}
A naive transformation would result in
export var foo = 10;
var x = {
foo: function foo() { return foo }
}
which is wrong.
I'll note that as a workaround, you could just use function declarations instead. I've never understood why a person would want to use the
const x = () => {
// ...
}
pattern given that function declarations often take fewer characters. 😄
Hm, interesting.
That example looks like it would actually work fine in ES5 emit but I see your point.
Looking at how Babel compiles these examples I see they always emit named functions, and they add a _ to avoid name scope clashes.
I've never understood why a person would want to use the
const x = () => { }pattern given that function declarations often take fewer characters
Well I used to feel that way, but it also lets you omit the { } brackets and return which makes for clean functional expressions, and then you want consistently so you start using it everywhere. :) And for whatever reasons this is the form that has become standard convention with React stateless functional components...
Duplicate of https://github.com/Microsoft/TypeScript/issues/6433
And for whatever reasons this is the form that has become standard convention with React stateless functional components...
I don't think there is any "standard" it's just how some of people are using it.
I for instance prefer function because the power of hoisting. Anyway React Stateless component is just a function not a special REACT pattern.
How we are dealing with this behaviour is explicitly exporting stuff which is also good pattern which makes every js file/module consistent -> imports on the top, exports at the bottom
import * as React from 'react';
import {SFC} from 'react';
import {SomeCmp} from './components';
type HelloProps = {who:string}
const Hello: SFC<HelloProps> = ({who,children}) => (
<div>
<h4>Who are you? -> {who}</h4>
<p>{children}</p>
</div>
);
export {
Hello,
HelloProps // this is needed to mage `declaration: true` work
}
I don't think there is any "standard" it's just how some of people are using it.
Absolutely, it's not a real "standard" just a (very) common pattern, especially for React SFCs but also JS functions in general. There's various good reasons for it (new and this restrictions, etc), and some tradeoffs, I personally don't have a strong preference either way; that's all besides the point, the point is that this common pattern and a particular intersection of common patterns (exported function expression) causes unexpected/undesirable behavior only in TS (ie not Babel). Not really TS's fault, either, but it's confusing, and could be fixed by TS (whether its worthwhile is still a legitimate question). Personally it took me (and others) awhile to even figure out what was going on here.
How we are dealing with this behaviour is explicitly exporting stuff which is also good pattern which makes every js file/module consistent -> imports on the top, exports at the bottom
Yeah that's the workaround I posted in the original thread, but again, its not as common a pattern and can cause some confusion (2 github "confused" emojis, the ultimate proof! /joking). It's sort of a hidden dependency for devs.
yup yup fair points, absolutely agree.
cheers man 🍻
there is also solution to use TS just for type checking and Babel for transpilation, but I personally try to avoid "double compilation"
@DanielRosenwasser the const foo = () => {…} pattern is nice because it allows you to specify the type of foo, which isn't possible with function foo() {…}, e.g.
const Foo: React.SFC = props => <p>{props.children}</p>
I just got bitten by the export const foo = () => {…} CJS emit being exports.foo = () => {…} which Chrome does not infer the name foo for. It would be very useful if the emit preserved the names at runtime.
I use this for storybooks and tests where I need to name the thing I'm demonstrating:
// test
describe(Foo.name, () => {
it("does something");
});
// storybook
storiesOf(Foo.name, module).add("default", () => <Foo />);
I like this pattern because it leverages the power of "rename", so that tests and storybooks are renamed properly when I perform a rename on the subject.
Most helpful comment
Hm, interesting.
That example looks like it would actually work fine in ES5 emit but I see your point.
Looking at how Babel compiles these examples I see they always emit named functions, and they add a
_to avoid name scope clashes.Well I used to feel that way, but it also lets you omit the
{ }brackets andreturnwhich makes for clean functional expressions, and then you want consistently so you start using it everywhere. :) And for whatever reasons this is the form that has become standard convention with React stateless functional components...