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. 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 code 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 doesn't gives any error, and I don't need to import the union type eitherfieldErrors: [ { 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' } ]