19

my codes:

export const LOAD_USERS = 'LOAD_USERS';
export const CREATE_USER = 'CREATE_USER';
export interface ACTION {
  type: string,
  payload: any
}

I want to restrict the ACTION.type to be either 'LOAD_USERS' or 'CREATE_USERS'. I can do this by String Literal type: 'LOAD_USERS'|'CREATE_USERS'. But can not do it with variables type: LOAD_USERS | CREATE_USERS. My editor prompts "can not find name 'LOAD_USERS'". Is there a way to use variables to do this? There could be typo when typing the same string in more than one place.

Konrad Krakowiak
  • 12,285
  • 11
  • 58
  • 45
Timathon
  • 278
  • 1
  • 2
  • 8
  • you want to specify type as some constant? You need to declare interface for each of these constants and then use those interface for further specifying type. that's the perfect way afaik. – Ajay Apr 08 '16 at 12:22
  • Thanks for comment. My ACTION.type is a string with limited value, 'LOAD_USERS' or 'CREATE_USERS'. Seems interface can not help here. – Timathon Apr 08 '16 at 13:46
  • export const check: string = "check"; export const check1: string = "check1"; export interface checking { type: check|check1; name: string; } In my case this code is working, see if it works for your. Thanks for your response, your case is unique – Ajay Apr 08 '16 at 13:51
  • Hi, Ajay. I hope I could write the way exactly as yours. `export interface checking { type: check|check1; name: string; }` This won't work. In vscode, I got "can not find name 'check'". – Timathon Apr 08 '16 at 13:59
  • it was not giving me an error when i first wrote it, now it is showing the same error here. scope of constant is outside from that interface, i guess that the reason it is not identifying. i have written a code, see if it works.. its working in my case. export interface checking{ export const check: string = "check"; export const check1: string = "check1"; type: check|check1; name: string; }... reference:-http://stackoverflow.com/questions/26471239/typescript-constants-in-an-interface – Ajay Apr 08 '16 at 14:13

5 Answers5

22

If you want to ensure that the string in your variables will be the action type, then you should use a type alias and explicitly type the variables with that type:

export type ActionNames = 'LOAD_USERS' | 'CREATE_USER';
export const LOAD_USERS: ActionNames = 'LOAD_USERS';
export const CREATE_USER: ActionNames = 'CREATE_USER';

export interface ACTION {
  type: ActionNames;
  payload: any;
}

If the strings in the variables don't match one of the strings in ActionTypes, then you'll get an error, which is desired to prevent mistakes. For example, this would error:

export type ActionNames = 'LOAD_USERS' | 'CREATE_USER';
export const LOAD_USERS: ActionNames = 'LOAD_USERS_TYPO'; // error, good

Update

Note that in newer versions of TypeScript the following is another option:

const actionNames = ['LOAD_USERS', 'CREATE_USER'] as const;
type ActionNames = typeof actionNames[number]; // typed as 'LOAD_USERS' | 'CREATE_USER'

Also, looking back on this question, you probably want to declare your actions with a common string literal type property that's differentiated by the string literal type (see discriminated unions).

For example:

interface LoadUsersAction {
    type: "LOAD_USERS";
}

interface CreateUserAction {
    type: "CREATE_USER";
    name: string;
    // etc...
}

type Actions = LoadUsersAction | CreateUserAction;

Also, I recommend not bothering with the variables. Using the strings directly is type safe.

David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • Thanks, David. Your approach surely keeps the typo away. Will use yours. Not able to vote up your answer due to my low reputation here. But you got my thumbs up. – Timathon Apr 08 '16 at 15:13
16

You can use the typeof operator, it returns the inferred type:

export const LOAD_USERS = 'LOAD_USERS';
export const CREATE_USER = 'CREATE_USER';
export interface ACTION {
  type: typeof LOAD_USERS | typeof CREATE_USER,
  payload: any
}
despairblue
  • 347
  • 2
  • 8
  • 3
    `typeof` doesn't work in this case since it returns the primitive type `"string"` of the variables. The interface would accept any string as its `type`. – Mika Kuitunen Jul 16 '18 at 10:06
  • 1
    @MikaKuitunen You can checkout an example [here](https://www.typescriptlang.org/play/index.html#src=export%20const%20LOAD_USERS%20%3D%20'LOAD_USERS'%3B%0D%0Aexport%20const%20CREATE_USER%20%3D%20'CREATE_USER'%3B%0D%0Aexport%20interface%20ACTION%20%7B%0D%0A%20%20type%3A%20typeof%20LOAD_USERS%20%7C%20typeof%20CREATE_USER%2C%0D%0A%20%20payload%3A%20any%0D%0A%7D%0D%0A%0D%0Aconst%20action1%3A%20ACTION%20%3D%20%7B%0D%0A%20%20%20%20type%3A%20%22Foo%22%2C%0D%0A%20%20%20%20payload%3A%20null%0D%0A%7D). It works. :) – despairblue Jul 22 '18 at 12:20
  • 6
    I stand corrected. It seems like the `const` keyword allows TS to set a very specific type, with `let` the behavior is what I thought it would be. This is actually not clear just by reading TS docs so thanks for clarifying. – Mika Kuitunen Jul 23 '18 at 07:33
8

Since TypeScript 2.4 you can use enums with string members. Enums define a set of named constants.

export enum UserActions{
    LOAD_USERS = "LOAD_USERS",
    CREATE_USER = "CREATE_USER"
}

export interface ACTION {
    type: UserActions,
    payload: any
}
Benjamin Pajk
  • 101
  • 1
  • 4
4

You cannot totally eliminate the duplication, but you can reduce it to the bare minimum:

type nameIt = 'LOAD_USERS' | 'CREATE_USERS'
export const LOAD_USERS = 'LOAD_USERS';
export const CREATE_USER = 'CREATE_USER';

This allows you to use the nameIt type elsewhere, but it does mean repeating the strings in your constants and in the type.

Alternatively, if you are writing a new piece of code, you might prefer to use an enum.

Fenton
  • 241,084
  • 71
  • 387
  • 401
3

Simplifying the existing answers further,

export type ActionTypes = 'LOAD_USERS' | 'CREATE_USER';

is sufficient by itself. To refer to a specific action type, just use the quoted string literal:

'LOAD_USERS'

In some other languages we avoid repeating such string literals and prefer to name them with a declaration. But that's because those other languages don't have string literal types! In TypeScript, 'LOAD_USERS' already is a compile-time name for a statically-checkable type. You don't necessarily need to give it another name.

Example:

declare function doAction(action: ActionTypes);

doAction('LOAD_USERS'); // okay

doAction('LOAD_USES'); // error: typo is caught by compiler
Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284