Timathon
Timathon

Reputation: 298

String Literal Types with variables in Typescript

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.

Upvotes: 21

Views: 19584

Answers (5)

David Sherret
David Sherret

Reputation: 106600

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.

Upvotes: 23

Benjamin Pajk
Benjamin Pajk

Reputation: 101

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
}

Upvotes: 8

despairblue
despairblue

Reputation: 357

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
}

Upvotes: 16

Daniel Earwicker
Daniel Earwicker

Reputation: 116654

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

Upvotes: 3

Fenton
Fenton

Reputation: 250822

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.

Upvotes: 4

Related Questions