There are several situations that will give you this particular error. In the case of the OP there was a value defined explicitly as a string. So I have to assume that maybe this came from a dropdown, or web service or raw JSON string.
In that case a simple cast <Fruit> fruitString
or fruitString as Fruit
is the only solution (see other answers). You wouldn't ever be able to improve on this at compile time. [Edit: See my other answer about <const>
] !
However it's very easy to run into this same error when using constants in your code that aren't ever intended to be of type string. My answer focuses on that second scenario:
First of all: Why are 'magic' string constants often better than an enum?
- I like the way a string constant looks vs. an enum - it's compact and 'javascripty'
- Makes more sense if the component you're using already uses string constants.
- Having to import an 'enum type' just to get an enumeration value can be troublesome in itself
- Whatever I do I want it to be compile safe so if I add remove a valid value from the union type, or mistype it then it MUST give a compile error.
Fortunately when you define:
export type FieldErrorType = 'none' | 'missing' | 'invalid'
...you're actually defining a union of types where 'missing'
is actually a type!
I often run into the 'not assignable' error if I have a string like 'banana'
in my typescript and the compiler thinks I meant it as a string, whereas I really wanted it to be of type banana
. How smart the compiler is able to be will depend on the structure of your code.
Here's an example of when I got this error today:
// this gives me the error 'string is not assignable to type FieldErrorType'
fieldErrors: [ { fieldName: 'number', error: 'invalid' } ]
As soon as I found out that 'invalid'
or 'banana'
could be either a type or a string I realized I could just assert a string into that type. Essentially cast it to itself, and tell the compiler no I don't want this to be a string!
// so this gives no error, and I don't need to import the union type too
fieldErrors: [ { fieldName: 'number', error: <'invalid'> 'invalid' } ]
So what's wrong with just 'casting' to FieldErrorType
(or Fruit
)
// why not do this?
fieldErrors: [ { fieldName: 'number', error: <FieldErrorType> 'invalid' } ]
It's not compile time safe:
<FieldErrorType> 'invalidddd'; // COMPILER ALLOWS THIS - NOT GOOD!
<FieldErrorType> 'dog'; // COMPILER ALLOWS THIS - NOT GOOD!
'dog' as FieldErrorType; // COMPILER ALLOWS THIS - NOT GOOD!
Why? This is typescript so <FieldErrorType>
is an assertion and you are telling the compiler a dog is a FieldErrorType! And the compiler will allow it!
BUT if you do the following, then the compiler will convert the string to a type
<'invalid'> 'invalid'; // THIS IS OK - GOOD
<'banana'> 'banana'; // THIS IS OK - GOOD
<'invalid'> 'invalidddd'; // ERROR - GOOD
<'dog'> 'dog'; // ERROR - GOOD
Just watch out for stupid typos like this:
<'banana'> 'banan'; // PROBABLY WILL BECOME RUNTIME ERROR - YOUR OWN FAULT!
Another way to solve the problem is by casting the parent object:
My definitions were as follows:
export type FieldName = 'number' | 'expirationDate' | 'cvv';
export type FieldError = 'none' | 'missing' | 'invalid';
export type FieldErrorType = { field: FieldName, error: FieldError };
Let's say we get an error with this (the string not assignable error):
fieldErrors: [ { field: 'number', error: 'invalid' } ]
We can 'assert' the whole object as a FieldErrorType
like this:
fieldErrors: [ <FieldErrorType> { field: 'number', error: 'invalid' } ]
Then we avoid having to do <'invalid'> 'invalid'
.
But what about typos? Doesn't <FieldErrorType>
just assert whatever is on the right to be of that type. Not in this case - fortunately the compiler WILL complain if you do this, because it's clever enough to know it's impossible:
fieldErrors: [ <FieldErrorType> { field: 'number', error: 'dog' } ]
export type Fruit
? – Weather Vane