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])
}