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 either
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' } ]