limitations of reverse mappings in string enums
API_MESSAGES[errorRes]
triggers an error due to the absence of reverse mappings for string enums, as explained in the Typescript Handbook:
Understanding Reverse Mappings
Numeric enum members possess both forward (name -> value) and reverse (value -> name) mappings, whereas string enum members lack reverse mapping functionality.
A numeric enum compiles into an object containing both forward and reverse mappings.
It's essential to note that string enum members do not receive automatic generation of a reverse mapping.
the importance of ignoring reverse mappings
The primary objective is to showcase the "translated" API message in a human-friendly manner. Even if support existed for string enums within API_MESSAGES[errorRes]
, the outcome would be:
'Token seems to be invalid' --> TOKEN_INVALID
This represents the opposite of the desired result.
Furthermore, backend alignment with enum definition simplifies matters!
If the back-end code aligns with the same API_MESSAGES
enum, it simply uses "a set of named constants", such as FAILED_TO_LOAD
and TOKEN_INVALID
, where these values serve as the translated messages themselves.
Hence, serialized API_MESSAGES
transmission from the back-end already contains the "translated" message.
Consequently, your onError
function becomes:
onError: (error: AxiosError<ApiError>) => {
const errorRes = error.response?.data.error;
console.log(errorRes)
}
Therefore, based on Typescript principles and logical deductions, error.response?.data.error
holds the type API_MESSAGES
. This implies that error.response?.data.error
effectively represents the following union type
type API_MESSAGES = 'Failed to load data' | 'Token seems to be invalid',
To reiterate, the translated value already exists. FAILED_TO_LOAD
and TOKEN_INVALID
merely serve as the Typescript names for those values. In case the back-end mirrors the enum definition.
Alternatively, underlying codes necessitate mapping objects
If FAILED_TO_LOAD
and TOKEN_INVALID
denote the actual message strings sourced from the backend, employing a mapping object instead of an enum proves beneficial:
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 transmitted from the backend materializes as a plain string, diverging from a typed Enum. The convertServerMessage
function showcases the validation and conversion process to the API_MESSAGE
enum. As previously mentioned, this activity requires execution at some juncture. Integrating this logic directly into the section of your code responsible for processing the server response and constructing the AxiosError object, wherein AxiosError.response.data.error
assumes the type API_MESSAGE
, obviates the need for a separate method.
export function convertServerMessage(msg: string): API_MESSAGE {
if (msg in API_MESSAGES) {
return msg as API_MESSAGE
} else {
// customized error handling can be implemented here
}
}
Subsequently, the initial onError
function operates as intended (refer to endnote):
onError: (error: AxiosError<ApiError>) => {
// For simplicity, consider relocating the string-to-enum conversion
// elsewhere within your codebase. It's currently placed here as an example.
const errorRes = convertServerMessage(error.response?.data.error);
console.log(API_MESSAGES[errorRes])
}