21

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.

5
  • 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. Commented Apr 8, 2016 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. Commented Apr 8, 2016 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 Commented Apr 8, 2016 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'". Commented Apr 8, 2016 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:-stackoverflow.com/questions/26471239/… Commented Apr 8, 2016 at 14:13

5 Answers 5

23

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
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
}

3 Comments

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.
@MikaKuitunen You can checkout an example here. It works. :)
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.
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
}

Comments

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.

1 Comment

Thanks for the answer. Will use your approach for now.
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

5 Comments

The only disadvantage is that without a const any refactoring is more tedious.
@Aaron in what way?
I haven't seen any IDE that can "refactor/rename" a string literal type. You have to global search and replace. But if you have a const most IDEs can "refactor/rename" and you don't have to go replace all usages manually.
Ah. I tend to use VS code, where the search/replace makes this seem very easy.
Yeah it's not the worst problem especially since the compiler guides you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.