There are numerous scenarios that can trigger this specific error. In the instance mentioned, a value was explicitly defined as a string. It is possible that this originated from a dropdown menu, web service, or direct JSON string.
In such situations, the only resolution is to perform a simple cast like <Fruit> fruitString
or fruitString as Fruit
(refer to other responses). There is no room for improvement during compilation in this case. [Note: Refer to my previous response regarding <const>
] !
However, encountering the same error becomes quite easy when utilizing constants in your code that were never supposed to be of string type. My approach delves into this alternate scenario:
Firstly: Why do 'magic' string constants often outshine enums?
- I prefer the concise and 'javascripty' appearance of a string constant over an enum
- It makes more sense if the component you're working with already employs string constants
- The need to import an 'enum type' just to access an enumeration value could pose challenges in itself
- Whatever I implement must be compile-safe - any addition or removal of a valid value from the union type, or typos, should trigger a compile error
Luckily, defining:
export type FieldErrorType = 'none' | 'missing' | 'invalid'
...essentially creates a union of types where 'missing'
is considered a type!
I frequently encounter the 'not assignable' error when a string like 'banana'
is present in my typescript, confusing the compiler which interprets it as a string when in reality, it should be of type banana
. The effectiveness of the compiler's interpretation will vary based on your code structure.
Here's an instance where I encountered this error today:
// results in the error 'string is not assignable to type FieldErrorType'
fieldErrors: [ { fieldName: 'number', error: 'invalid' } ]
Upon realizing that 'invalid'
or 'banana'
could represent either a type or a string, I opted to assert a string into that type. Essentially, I would cast it to its original form, informing the compiler that I do not intend for this to remain a string!
// hence, this eliminates the error without importing the union type
fieldErrors: [ { fieldName: 'number', error: <'invalid'> 'invalid' } ]
Why not simply 'cast' to FieldErrorType
(or Fruit
)
// why isn't this advisable?
fieldErrors: [ { fieldName: 'number', error: <FieldErrorType> 'invalid' } ]
This method lacks compile-time safety:
<FieldErrorType> 'invalidddd'; // COMPILER ACCEPTS THIS - NOT IDEAL!
<FieldErrorType> 'dog'; // COMPILER ACCEPTS THIS - NOT IDEAL!
'dog' as FieldErrorType; // COMPILER ACCEPTS THIS - NOT IDEAL!
Why? Because in typescript, <FieldErrorType>
serves as an assertion, indicating that a dog belongs to FieldErrorType! The compiler doesn't object to this!
However, by executing the following approach, the compiler converts the string to a type
<'invalid'> 'invalid'; // THIS IS FINE - APPROPRIATE
<'banana'> 'banana'; // THIS IS FINE - APPROPRIATE
<'invalid'> 'invalidddd'; // ERRONEOUS - APPROPRIATE
<'dog'> 'dog'; // ERRONEOUS - APPROPRIATE
Exercise caution to avoid careless errors like:
<'banana'> 'banan'; // COULD LEAD TO RUNTIME ERROR - YOUR RESPONSIBILITY!
Another workaround involves casting the parent object:
My definitions stood as follows:
export type FieldName = 'number' | 'expirationDate' | 'cvv';
export type FieldError = 'none' | 'missing' | 'invalid';
export type FieldErrorType = { field: FieldName, error: FieldError };
Suppose we face an error similar to this (the string not assignable error):
fieldErrors: [ { field: 'number', error: 'invalid' } ]
We can assert the entire object as a FieldErrorType
in this manner:
fieldErrors: [ <FieldErrorType> { field: 'number', error: 'invalid' } ]
Thus, we avoid resorting to <'invalid'> 'invalid'
.
Concerning potential typos - doesn't <FieldErrorType>
merely serve to assert whatever appears on the right as that particular type? Not in this context - thankfully, the compiler WILL raise objections in instances where it deems impossibility:
fieldErrors: [ <FieldErrorType> { field: 'number', error: 'dog' } ]