String enums continue to be incredibly tedious in Flow. Using type unions of string literals only goes so far because you can't actually assert that a given string is a valid member of the enum without repeating yourself. Ideally, what I would like to write is something like this:
const VALID = ['string', 'enum'];
type ValidType = $Values<typeof VALID>;
function readString(filename: string): ValidType {
const content = fs.readFileSync(filename); // `content` is just `string` for now
invariant(VALID.includes(content)) // `content` is now proven to be of ValidType
return content;
}
This solution isn't necessarily less tedious, but it does allow you to assert a given string is a valid member of an enum without repetition:
const VALID = {
string: '',
enum: '',
};
type ValidType = $Keys<typeof VALID>;
const VALID_VALUES: Array<ValidType> = Object.keys(VALID);
function readString(filename: string): ValidType {
const content = fs.readFileSync(filename); // `content` is just `string` for now
const validatedContent = VALID_VALUES.find(value => value === content);
invariant(validatedContent) // `content` is now proven to be of ValidType
return validated;
}
@rsolomon Ah, that's a neat trick, making VALID_VALUES an Array<ValidType> and then "laundering" the type of content through VALID_VALUES.find(). Could write a small helper for that:
function ensureEnum<E>(value: string, enums: Array<E>): E {
const literal = enums.find((candidate) => candidate === value);
invariant(literal);
return literal;
}
So then we have
const VALID = {
string: '',
enum: '',
};
type ValidType = $Keys<typeof VALID>;
const VALID_VALUES: Array<ValidType> = Object.keys(VALID);
function readString(filename: string): ValidType {
const content = readFileContents(filename); // `content` is just `string` for now
return ensureEnum(content, VALID_VALUES);
}
Still quite cluttered. Could push VALID_VALUES into the helper as well if performance/GC garbage isn't a concern:
function ensureEnum<E: Object>(value: string, enums: E): $Keys<E> {
const valids: Array<$Keys<E>> = Object.keys(enums);
const literal = valids.find((candidate) => candidate === value);
invariant(literal);
return literal;
}
In any case we have to do an Array.find(). It's really too bad that we have to add to the runtime to make Flow happy. In practice it probably won't matter much, but the principle of it does bother me. Plus, string enums are SUCH a common thing in JS.
I have the same issue and the only way to overcome this is to dupe the values twice which brings the burden of maintaining these two values now.
It's quite frustrating that I have to fight the Flow each time I need to share constants between JS and Flow, this greatly decreases my productivity.
Most helpful comment
I have the same issue and the only way to overcome this is to dupe the values twice which brings the burden of maintaining these two values now.
It's quite frustrating that I have to fight the Flow each time I need to share constants between JS and Flow, this greatly decreases my productivity.