Skip to content

Instantly share code, notes, and snippets.

@lucianoratamero
Last active December 20, 2022 13:21
Show Gist options
  • Save lucianoratamero/1ce44f3050fcfc1fc01bcdd0b6d37320 to your computer and use it in GitHub Desktop.
Save lucianoratamero/1ce44f3050fcfc1fc01bcdd0b6d37320 to your computer and use it in GitHub Desktop.
How to iterate through string literals in TypeScript

How to iterate through string literals in TypeScript

// In edge cases, it would be useful to iterate through a string literal,
// so you can do something based on each value.

// Let's say you have a literal of UserRoles

type UserRole = 'user' | 'moderator' | 'admin';

// With that, you might want to, for example, get the permissions for each one.
// Let's pretend to implement one:

function getPermissionByUserRole(userRole: UserRole): string {
    // since we are faking it, let's just return the role
    return userRole;
}

// Here's the catch: based on the code we have, we can't just iterate through the user roles.
// TypeScript doesn't send the types to runtime, so UserRole is simply not available to be iterated.

// If you try to map it, like this:
// UserRole.map(getPermissionByUserRole);
// You get: `'UserRole' only refers to a type, but is being used as a value here.`

// To work around that, let's first create a constant with the values, so they're available at runtime:

const USER_ROLES_FAIL = ['user', 'moderator', 'admin'];

// That's not enough, since TypeScript infers it as a `string[]`. So, we add `as const`:

const REAL_USER_ROLES = ['user', 'moderator', 'admin'] as const;

// Cool, now we have a constant with the readonly list of `['user', 'moderator', 'admin']`.
// We could already iterate through it, but let's first create a type for it:

type RealUserRole = typeof REAL_USER_ROLES[number];

// This line is a bit hard, so let's go from the right to the left:
// REAL_USER_ROLES[number], if it was accessed like that (REAL_USER_ROLES[0] for example),
// would return the item for that index value: 'user';
// That means that REAL_USER_ROLES[number] can be either 'user' OR 'moderator' OR 'admin'.
// Convert that to TypeScript and that's almost literally `'user' | 'moderator' | 'admin'`.

// REAL_USER_ROLES[number] is not valid JS syntax, though.
// So we just need to add the typeof.
// If you don't trust me (as you shouldn't), just hover above UserRole and RealUserRole.
// You'll see they're the same :]

// So much the same, in fact, that if we iterate through REAL_USER_ROLES
// using the function with the UserRole type notation,
// it just works!

console.log(`And the iteration returns: ${REAL_USER_ROLES.map(getPermissionByUserRole)}`);

// I also invite you to look at the output generated by TypeScript, it's always fun!
  
Output
"use strict";
// In edge cases, it would be useful to iterate through a string literal,
// so you can do something based on each value.
// With that, you might want to, for example, get the permissions for each one.
// Let's pretend to implement one:
function getPermissionByUserRole(userRole) {
    // since we are faking it, let's just return the role
    return userRole;
}
// Here's the catch: based on the code we have, we can't just iterate through the user roles.
// TypeScript doesn't send the types to runtime, so UserRole is simply not available to be iterated.
// If you try to map it, like this:
// UserRole.map(getPermissionByUserRole);
// You get: `'UserRole' only refers to a type, but is being used as a value here.`
// To work around that, let's first create a constant with the values, so they're available at runtime:
const USER_ROLES_FAIL = ['user', 'moderator', 'admin'];
// That's not enough, since TypeScript infers it as a `string[]`. So, we add `as const`:
const REAL_USER_ROLES = ['user', 'moderator', 'admin'];
// This line is a bit hard, so let's go from the right to the left:
// REAL_USER_ROLES[number], if it was accessed like that (REAL_USER_ROLES[0] for example),
// would return the item for that index value: 'user';
// That means that REAL_USER_ROLES[number] can be either 'user' OR 'moderator' OR 'admin'.
// Convert that to TypeScript and that's almost literally `'user' | 'moderator' | 'admin'`.
// REAL_USER_ROLES[number] is not valid JS syntax, though.
// So we just need to add the typeof.
// If you don't trust me (as you shouldn't), just hover above UserRole and RealUserRole.
// You'll see they're the same :]
// So much the same, in fact, that if we iterate through REAL_USER_ROLES
// using the function with the UserRole type notation,
// it just works!
console.log(`And the iteration returns: ${REAL_USER_ROLES.map(getPermissionByUserRole)}`);
// I also invite you to look at the output generated by TypeScript, it's always fun!
Compiler Options
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}

Playground Link: Provided

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment