71

I have a problem where the Typescript compiler compiles my code successfully, yet the runtime gives me undefined type errors.

In my app I created a types.ts file with some things shared between multiple other ts files. It contains a string enum like:

enum MyEnum {
  One = "one";
  Two = "two";
}

When I define it like this. The compiler lets me use it in other ts files, and appears to be happy. However, at runtime I get the error "MyEnum is not defined".

I know of two ways to solve this:

  1. Define the enum in the file where it is used. But I don't think this will solve anything for other files that want to use it.
  2. Use "export" in the types.ts file, and import every type explicitly everywhere it is used.

I am quite new to Typescript, and I feel I might be misunderstanding something fundamental.

First, I don't get why the Typescript compiler happily compiles my code if there's going to be a runtime error. I would understand it if I had used the declare keyword, telling the compiler that something should be available at runtime, but in this case I don't see why it should assume that the enum comes from anywhere else then the types.ts file.

Second, I would like to define types somewhere globally in my app and have them be available everywhere without having to import them every time I used them. How do I accomplish this? Or is this maybe considered bad practice?

I am using Typescript 2.6 and my config looks like this:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es6",
    "module": "commonjs",
    "lib": ["es6", "es7", "esnext"],

    "sourceMap": true /* Generates corresponding '.map' file. */,
    "outDir": "build" /* Redirect output structure to the directory. */,
    "removeComments": true /* Do not emit comments to output. */,

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,

    /* Additional Checks */
    "noUnusedLocals": true /* Report errors on unused locals. */,
    "noUnusedParameters": true /* Report errors on unused parameters. */,
    "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
    "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,

    "plugins": [{ "name": "tslint-language-service" }],
    "skipLibCheck": true // because firebase-sdk has wrong type files now (Nov 18)
  },
  "include": ["src/**/*"],
  "exclude": ["build"]
}
Thijs Koerselman
  • 21,680
  • 22
  • 74
  • 108
  • The enum will be transpiled as an object. If you are using as a node app you must export and import it in the files where you want to use. If you don't, it would only work in a browser environment where a script with your transpiled file containing the enum is placed before all other files that reference the object. –  Nov 21 '17 at 16:57
  • OK the object makes sense to me. I was wondering why this only occurred in my server code and not the web client. But could you explain a bit more about how this works in the browser, or provide a link to more info? Do enums become globally declared variables in a browser environment? – Thijs Koerselman Nov 22 '17 at 09:43
  • No, you have to put a script tag with the file transpiled with the enum object _before_ the script tag that references it ... –  Nov 22 '17 at 09:57
  • 1
    Ah ok. But technically still a global variable then, just maybe hidden somewhere :) I'm using the Typescript variant of [CRA](https://github.com/wmonk/create-react-app-typescript) so I guess it's dealt with already. I still wonder what a nice solution is on the server side. By using an enum (and the export) I am forced to now also export and import all of the other interface and type declarations that live in that file... – Thijs Koerselman Nov 22 '17 at 10:06
  • I've been using `/// ` at the top of the dependent TypeScript file to reference classes and functions and they work fine. However, for some reason it doesn't work for enums! – Matt Arnold Sep 05 '19 at 09:58
  • Disregard my last comment about `reference path` not working for enums; I forgot to include a reference to the resultant JavaScript file in my `BundleConfig.cs` file for my MVC project. This was the cause of the problem for me. – Matt Arnold Sep 05 '19 at 10:41

9 Answers9

60

There's another way you can do this. If you don't want to export your enum you can define it as a const enum

const enum MyEnum {
   One = "one";
   Two = "two";
}

These are inlined by the compiler and are completely removed during compilation.

Matt B
  • 8,315
  • 2
  • 44
  • 65
  • 2
    Thanks, I had since discovered that too. I also use it for all enums that I export. But I forgot that this question didn't have an accepted answer yet... – Thijs Koerselman May 25 '18 at 15:24
  • 4
    Beware of using this solution if you need to parse a string into the enum. I now get `'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment.` for the expression: `(MyEnum)[myString] === MyEnum.One`. – Matt Arnold Sep 05 '19 at 09:31
  • @MattArnold the error is that you can only use string literals to index a const enum. That error is probably being hidden due to the fact you're casting to *any*, something I would generally try to avoid. It may be that *string literal types* are more suited to your use-case. – Matt B Sep 05 '19 at 12:08
  • @nukefusion Ah that's disappointing; I thought the whole point of having the `[]` syntax as an alternative to `.` was to allow the dynamic access of members via a variable. In my scenario, I didn't need the `const` in the end anyway, I had just forgotten to include the enum file in my `BundleConfig.cs`. I did still need the `` cast to make it work though (which seems like a bit of a hack!). – Matt Arnold Sep 05 '19 at 14:23
  • I had this same issue. When I tried making them into const enums it still didn't work. I had to wipe away my build output directory (+build cache?) then build. – Pend Jun 26 '20 at 06:14
  • 2
    using `const enum` did work.. but why does this work instead of using just `enum`? – guijob Jul 21 '20 at 18:43
  • I stopped using enums altogether. The added complexity is not worth it for me. I seem to be able to do everything with string literals and it appears cleaner too. – Thijs Koerselman May 31 '22 at 14:33
58

In my case of undefined enum, it turned out it's because of circular import:

export enum A {...} defined in file a.ts, export const b = ... defined in file b.ts;

import {A} from './a.ts' in b.ts, while import {b} from './b.ts' in a.ts.

The error was gone after removing circular imports.

cuz
  • 1,172
  • 11
  • 12
17

I had the same problem when imported re-exported enum. It caused runtime error.

layout.ts

export enum Part { Column, Row }

index.ts

export * from './layout'

component.ts

import { Part } from '../entities' // This causes error
import { Part } from '../entities/layout' // This works
sad comrade
  • 1,341
  • 19
  • 21
9

I had this issue because I used declare in

export declare enum SomeEnum

Instead of

export enum SomeEnum
Steffs
  • 91
  • 1
  • 2
  • Do you know why using the declare keyword causes this issue? I am unable to import from a package because of it. – kmars Aug 28 '23 at 09:10
4

For us it turned out that simply restarting the application solved the problem. (Nativescript app)

Baki
  • 564
  • 2
  • 10
  • 22
2

I had this error and it went away as soon as I used the export keyword, i.e.

export enum MyEnum {
  One = "one";
  Two = "two";
}

And make sure you import it in the files where you are using it as well, i.e.

import { MyEnum } from '../types.ts';

I found that when I declared the enum without the export keyword, I could still reference the enum without importing it in other files without a compiler error - it was only at runtime the undefined exception was then thrown.

Ciaran Gallagher
  • 3,895
  • 9
  • 53
  • 97
  • 1
    Thanks, but if you read the comments below the question you see that I already got this far :) I am now mainly wondering if there is no way to globally define types in the app without having to export/import everything, because it is really cumbersome to do this for all types. I guess you could just put all the enums in a separate file and only export/import that. This way all other types are still globally available and you don't need to write all of the boilerplate to use them across the app. – Thijs Koerselman Nov 25 '17 at 12:41
  • Out of curiosity, what IDE are you using? With Visual Studio Code, when you type out the name of the enum, VS Code will automatically generate the import statement for you, which saves a considerable amount of time when coding, and helps to keep a smooth uninterrupted flow. I was originally using JetBrains WebStorm but found I was more productive in the Visual Studio Code IDE. – Ciaran Gallagher Nov 25 '17 at 13:51
2

Here is my case, when I add a new enum to enum.ts file like this:

enum.ts:

enum RotateMode {
  UNKNOWN = 'UNKNOWN',
  OPTIMIZE = 'OPTIMIZE',
  ROTATE_FOREVER = 'ROTATE_FOREVER'
}

export { RotateMode };

I didn't run tsc to compile this file again. This cause the enum.js file doesn't have the RotateMode enum

enum.js:

// old file doesn't have the RotateMode enum

Then, I import the RotateMode enum to my index.ts file:

import { RotateMode } from './enum';

console.log(`RotateMode: ${JSON.stringify(RotateMode)}`);

The result is:

RotateMode: undefined

Somehow, the import syntax will import from .js file with high priority.

So, DON'T forget to compile the ts file after you add something new

Lin Du
  • 88,126
  • 95
  • 281
  • 483
0

I may be late to the party but beware of any @ts-ignore lines in the files blamed by the error stack.

My issue was that, after a pretty big refactoring, the import to the enum was cleaned by an IDE shortcut and the compiler couldn't detect it as missing because of the ts-ignore annotation which was targeted at something else (alas, on the same line).

0

I was receiving a similar error message while importing an enum from another project of mine. I was exporting the enum, but it was not declared as a "const". I updated my enum in my other project to the below code and it worked as expected.

export const enum MyEnum {
  One = 'one',
  Two = 'two'
}
Jason Spence
  • 472
  • 5
  • 16