Let's say a value can be one of a finite number of string values:
type State = 'disconnected' | 'connecting' | 'connected';
let state: State = ...;
If I already have an array of all valid states in my code somewhere, it'd be nice if it could be used to create a type union from that, e.g.:
const STATES = ['disconnected', 'connecting', 'connected'];
type State = $Values<STATES>;
let state: State = ...;
I'm proposing $Values
here analogously to $Keys
. Alternatively, $Either
could be extended to not just accept a list of types but also an array.
Is there any way to do this yet? I want to define a type where the values in one argument, an array, are passed in as keys of an object in a function later down.
Bump. This would work well with the proposed $Values
.
Will this thing happen at any point? Been rotting for a while and similar requests are all around the flow issues
@calebmer landed ab0789a today, which I believe should make it out with the next release of flow. I know I personally can't wait to use it 🎉
thanks @calebmer!
Interestingly, there are no tests for $Values<T>
on an Array?
Interestingly, there are no tests for $Values
on an Array?
Because it doesn't work with arrays, only with objects
Is that planned?
On Jun 20, 2017 12:05 AM, "Vladimir Kurchatkin" notifications@github.com
wrote:
Interestingly, there are no tests for $Values on an Array?
Because it doesn't work with arrays, only with objects
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/facebook/flow/issues/961#issuecomment-309645745, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABJFP23DwjhhsgFbvM0Oe-D1N34ENylcks5sF1MHgaJpZM4GSaOq
.
Here is a simple example of how $Values
works now:
const MyEnum = {
foo: 'foo',
bar: 'bar'
};
type MyEnumT = $Values<typeof MyEnum>;
('baz': MyEnumT); // No error
For the same reason even if it worked with arrays, it wouldn't work the way you want.
Assumedly that's because the type of:
const Suite = {
DIAMONDS: 'Diamonds',
CLUBS: 'Clubs',
HEARTS: 'Hearts',
SPADES: 'Spades',
}
is not what you might think, it's:
{
DIAMONDS: string,
CLUBS: string,
HEARTS: string,
SPADES: string,
}
which speaks to the need for some kind of helper to get the actual values of an object when using typeof
, rather than the types of those values.
@STRML I don't think that's right. See https://github.com/facebook/flow/blob/ab0789ade95090c2a07ce1dff0e6511226ed73fd/tests/values/object_types.js#L80
Yeah I'm not sure what the current $Values
implementation gives us. It works well for the way Facebook likes to define enums through alias objects, but I don't understand how, for instance, you can assert that a given string
value is in fact a valid member of an enum.
For instance, imagine you're reading a value from a file. Using my original example, I'd like to see something as succinct as this:
const STATES = ['disconnected', 'connecting', 'connected'];
type State = $Values<STATES>;
function readState(filename: string): State {
const state = fs.readFileSync(filename); // `state` is just `string` for now
invariant(STATES.includes(state)) // `state` is now proven to be of State
return state;
}
I've filed this separately in #4454.
@philikon But that's because they're actually defining the type's values directly. See this example, which throws no errors, for what happens if you don't.
@STRML ah yes you're right. I guess that's due to #2639. What a mess.
Does this also cover the case of dynamically building a union type?
Example:
// A list of existing flow types
const accountTypes = [CheckingAccount, BankingAccount, InvestmentAccount];
// What I have to do now
export type Account = CheckingAccount | BankingAccount | InvesmentAccount;
// My software has a plugin system, so any time a new plugin is added,
// I have to go update this list as well.
// What would be nice is some way to build a union type dynamically from an array
export type Account = accountTypes.reduce((flowUnion, accountType) => (
flowUnion.add(accountType);
), new FlowUnion());
// Or...
export type Account = $Union<accountTypes>;
I think that's what this ticket is requesting, right? It's just calling it $Values. Or am I mistaken?
This issue should probably be closed now that $Values
has been shipping for a while.
// Note: must use Object.freeze for Flow to know the value will not change.
const MyObj = Object.freeze({
DIAMONDS: 1,
CLUBS: 2,
HEARTS: 3,
SPADES: 4,
})
type MyObjType = $Values<typeof MyObj>
// No Error
const testMyObjEntry1: MyObjType = 3
// Expect Error
const testMyObjEntry2: MyObjType = 5
type MyObjKey = $Keys<typeof MyObj>
// No Error
const testMyObjKey1: MyObjKey = 'HEARTS'
// Expect Error
const testMyObjKey2: MyObjKey = 'JOKERS'
https://flow.org/try/#0PTAEDkHsBcFMC5QFsCuBnap21AeQEYBWsAxtAHQBmATrLAF46WTWgBiANpAO6jSSgA1gDsefABY4AbgEMOKHNwCWHDqFGYS4mcIDmscgCgSkYRlABZAJ4FCoALx4ipCjTqMAFAG9DoUABEASQBBC1xwfwBlRABGABpfUABhABkAVQAhaNAAJgS-AAkAUWCAJQAVbIBmfNBIgAVg-yLsgBYEgF8ASkNDaCsABxxrW3LBnEcAEgA1OQU0AB5+ochKSxsiAD5DEAgBIupqFmNTczgMEaIi4WhqKxjES8IxoYdQKp2wIoAPIbJQA5HagnMyYc7QJ7XW5WHKPDbPcZvACsvWWw3hAGlYFY3pMsVZFmjVutbNtdlAAYdjiZQXxYBdMdiHiSiPi3gByYplSrsz4A34uSlAkFnekQxkwuG2NmOdkAKVwGKKpUivKAA
I'm not so sure: $Values can't work with arrays and the title of this issue is "Union from array literal"
@leebyron Specifically, the feature request is to be able to say:
const suites = Object.freeze([
'DIAMONDS',
'CLUBS',
'HEARTS',
'SPADES',
})
type Suit = $ArrayValues<typeof suites>
// No Error
const testDiamond: Suit = 'DIAMONDS'
// Expect Error
const testBogus: Suit = 'BOGUS'
I'm having issues with this when working with Mongoose. Their enum property for a field accepts an array of strings meaning there is no way for me to share this information with my class definition. There is no way around using an Array literal without adding extra logic for nothing other than a type.
I wish I could write something like this without having to maintain two separate lists.
import mongoose from 'mongoose';
const schemaDefinition = {
type: {
type: String,
enum: [
'car',
'truck',
'van',
],
required: true,
},
};
const schema = mongoose.Schema(schemaDefinition);
class VehicleClass {
/** the type of vehicle */
type: $Values<schemaDefinition.type.enum>;
}
schema.loadClass(VehicleClass);
mongoose.model('Vehicle', schema);
I've been using this workaround:
// create an object for the sake of flow
const namesObj = {
'ava': '',
'billy': '',
}
// create an array for actual use
const names = Object.keys(namesObj)
type Name = $Keys<typeof namesObj>
const sayMyName = (name: Name) => {
console.log(name)
}
sayMyName('ava') // works
sayMyName(names[0]) // works
sayMyName('baz') // nah
It's not ideal, but at least means I'm only updating the 'array' in one place.
+1 for a utility to get a Union from an array literal!
@good-idea this is workable but very regrettably introducing runtime overhead to allow something that we should be able to get for free at build time...
if $ObjMap
were expanded to work on array literals it could perhaps be achieved
A different use case but if you have the array defined as a type we could make it work with $Call
like this:
type Enum = ['A', 'B']
const $getArrayVals = x => x.map(t => t)[0]
type $ArrayVals<T> = $Call<typeof $getArrayVals, T>
type $Letter = $ArrayVals<Enum>
const letter1: $Letter = 'A'
const letter2: $Letter = 'C' // error here
The problem is that it does not work if you try to use typeof myEnum
because you'll get an array of strings here, but maybe something could be worked from that?
Note: Just in case, I also tried defining an array with casted strings but didn't work at all:
const myEnum = [('A': 'A'), ('B': 'B')]
bump. neeeeeeed this
Typescript has it since 3.4
Is this a solution for y'all? It works for my usecase.
/* @flow */
type Events = [
{
type: 'POPULATE_VPX_ACCESS_TOKEN',
userAccessToken: string,
pageAccessToken: string,
},
{
type: 'LOAD_BROADCAST',
broadcastId: string,
},
];
type InboundCreatorStudioEvents = {
origin: string,
data: $ElementType<Events, number>
};
function doThings(event: InboundCreatorStudioEvents) {
const origin = event.origin;
if (origin.endsWith('facebook.com')) {
switch (event.data.type) {
case 'POPULATE_VPX_ACCESS_TOKEN':
const data = event.data;
console.log(data.userAccessToken);
console.log(data.pageAccessToken);
// $ExpectError
console.log(data.broadcastId);
break;
case 'LOAD_BROADCAST':
console.log(event.data.broadcastId);
// $ExpectError
console.log(event.data.pageAccessToken);
break;
default:
return;
}
}
}
const populateVpxAccessTokenEvent: InboundCreatorStudioEvents = {
origin: 'facebook.com',
data: {type: 'POPULATE_VPX_ACCESS_TOKEN', userAccessToken: '1234', pageAccessToken: '1234'}
}
const loadBroadcastEvent: InboundCreatorStudioEvents = {
origin: 'facebook.com',
data: {type: 'LOAD_BROADCAST', broadcastId: '1234'}
}
doThings(populateVpxAccessTokenEvent);
doThings(loadBroadcastEvent);
https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgCiAbngHYYDOYAvGANqphgDeTzY2+AXGAOQAFAPICAqgBkAggBVCAfQBqAgBpzJAYXWEAytrnShAaUIA5PgBp2zAK6U8AJ0kBjJ3kqVpcANblelDPYAlmQA5pYcYDgAhiF4zq7unj5kfgHBYewAvuGsVpy4eLx84kKSACJyAEIASqVl6pLa0hZ5AEb2cFEAJk5R-gCSXalBoTnZqAC6ANzoXAT9ZK1w1mRd6vZ4URhw9toY1l2BcCTkVLS5zDuBIcHD6TldW1G8ACSEMHgAtqfSBQA8JwolHMYDI1k+rQcAD5UJkZhEmFAVk4MEcyGAunBpAALdKUAAUeFIFF4CyWKzWGy2Oz2ByOgKoAEpcgBIJxwMj+MBXG7ouhE04AOh5wXhHECUDA+JFZEF5C6lAA6oEMNj8XwoFFXEtvIL2Z8+IzmWwIsxKAgVU5sVKBRRBY8MFFBXNjXkIr07PxhGIpLJFCo1JodHoDMYzNw3ab2ZyMBinudbRh7U8xaaONHKHAPoL4CF8Q6nbYHPE3B5vORGam02AM1m8Dm4HmC4LorES4ly2RK5GIsBgGA3gAPfAowj2Dr2HvpjmZ7O5-NPQXtTo9PoYQbd6scdqbLxV919AjFOpVWrlBpNPgRrfMWtzxuE4lJ5vL7oe9ddTc3vsDwjDvCjuOOxTreM51g2eaJsmjotjEcQuKWSQVvupo7lEe5Tl0eCatYMAYNeN4bPs9hkChmSoCy5HkQiqAZrGOBwDguFbHgCg4IO7ZlskDKkosyyrOsmzbLs+yHMcT7UHQJqmjKRSatqcC6vqFhgJGBa8CwcxFN6EgyPISiqBoWi6PoRimCpRaOAhHbJEUACMABMADMAAsKmtvBCRcb4-COa5fDURw5F0WA8DdJUHRvmuPFgGS-GUkJNKifSEnnNJESyfw8l4DqXh6nABogmpTwaVp-AlOUp51BezQgq+q4DEMvnOW5gXMORqCYjieL4gxTEwCxbEcdZ3lkAy3ZdbioQEmFXQRSu77jVWQA
@randalib, this requires writing type first, not getting it from literal
Has there been any update on this feature request? This would be extremely useful and a great value-add to Flowjs.
I'd like to have an array of possible values be the single source of truth if a field can only have the values represented in the array.
// @flow
const POSSIBLE_VALUES = Object.freeze(['Value1', 'Value2', 'Value3'])
type FormData = {
fieldKey: $ArrayValues(POSSIBLE_VALUES) // or something to this effect
}
I keep forgetting this is not possible in flow, then I search for it and I always land on this issue. Any chance that tis is going to be implemented at any point?
I've been using this workaround:
// create an object for the sake of flow const namesObj = { 'ava': '', 'billy': '', } // create an array for actual use const names = Object.keys(namesObj) type Name = $Keys<typeof namesObj> const sayMyName = (name: Name) => { console.log(name) } sayMyName('ava') // works sayMyName(names[0]) // works sayMyName('baz') // nah
It's not ideal, but at least means I'm only updating the 'array' in one place.
+1 for a utility to get a Union from an array literal!
This is nice, but the outcome of it is very confusing.
For example:
It says the type is the actual_value: string, instead of an enum of actual values.
And when you use it wrong, the error doesn't tell you anything about the enum, just that it does not exist on object literal:
At least, if the name is close enough you get a hint.
Most helpful comment
I'm not so sure: $Values can't work with arrays and the title of this issue is "Union from array literal"