Home > OS >  Access a TypeScript enum with a variable value
Access a TypeScript enum with a variable value

Time:12-10

I have an enum with a bunch of API messages I have translated to show on the front-end:

export enum API_MESSAGES {
  FAILED_TO_LOAD = 'Failed to load data',
  TOKEN_INVALID = 'Token seems to be invalid',
}

I would like to use the error coming from the back-end to get the translated value:

onError: (error: AxiosError<ApiError>) => {
  const errorRes = error.response?.data.error;
  console.log(API_MESSAGES[errorRes])
}

However this throws the following TS error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof API_MESSAGES'

No index signature with a parameter of type 'string' was found on type 'typeof API_MESSAGES'

I have tried searching but I didn't find an easy way to access the enum.

CodePudding user response:

string enums do not support reverse mappings

API_MESSAGES[errorRes] results in an error because, per the Typescript Handbook:

Reverse mappings

In addition to creating an object with property names for members, numeric enums members also get a reverse mapping from enum values to enum names.

A numeric enum is

compiled into an object that stores both forward (name -> value) and reverse (value -> name) mappings.

Keep in mind that string enum members do not get a reverse mapping generated at all.

but you don't want a reverse mapping anyway!

Your goal is to display the "translated" (human friendly) API message. So even if API_MESSAGES[errorRes] was supported for string enums, you would be doing this:

'Token seems to be invalid' --> TOKEN_INVALID 

i.e. the opposite of what you want.

If your back-end is using the same enum definition, it would be sending the "translated" value already!

Assuming your back-end code is using the same API_MESSAGES enum, then really all it is doing is using "a set of named constants", e.g. FAILED_TO_LOAD and TOKEN_INVALID, whose values are the translated messages themselves.

In other words, if your back-end is simply serializing the API_MESSAGES in its response to the front-end, it is sending the "translated" message already.

Which means your onError function would just be:

onError: (error: AxiosError<ApiError>) => {
  const errorRes = error.response?.data.error;
  console.log(errorRes)
}

From the Typescript perspective and what your logic implies, error.response?.data.error has the type API_MESSAGES. Because Typescript enums are really just named constants, what this means is that error.response?.data.error would effectively be the following union type

type API_MESSAGES = 'Failed to load data' | 'Token seems to be invalid',

So again, you would already have the translated value. FAILED_TO_LOAD and TOKEN_INVALID would be just the Typescript names for those values. If your back-end is using the same enum definition.

If your back-end is sending codes, the what you need is simply a mapping object

If FAILED_TO_LOAD and TOKEN_INVALID are the actual message strings being sent by the backend, you just need a mapping object instead of an enum:

export const API_MESSAGES = {
  FAILED_TO_LOAD = 'Failed to load data',
  TOKEN_INVALID = 'Token seems to be invalid',
} as const;

// type API_MESSAGE = 'FAILED_TO_LOAD' | 'TOKEN_INVALID'
export type API_MESSAGE = keyof typeof API_MESSAGES

The error code coming across the wire from the backend is just a plain string, not a typed Enum. This convertServerMessage function demonstrates how to validate and convert it to the API_MESSAGE enum. As you said earlier, this is something you have to do somewhere. You probably won't need this separate method -- Instead you can just put its logic in that part of your code that reads the server response and constructs the AxiosError object, whose AxiosError.response.data.error should have the type API_MESSAGE.

export function convertServerMessage(msg: string): API_MESSAGE {
    if (msg in API_MESSAGES) {
        return msg as API_MESSAGE
    } else {
        // error logic goes here
    }
}

then your original onError will work as you wanted (see note at bottom):

onError: (error: AxiosError<ApiError>) => {
    // as I said above, the conversion from string code to 
    // enum should probably happen elsewere. I have it here
    // to keep the example simple.
    const errorRes = convertServerMessage(error.response?.data.error);
    console.log(API_MESSAGES[errorRes])
}
  • Related