Is there any way I can do something like this:
// #flow
function FailureActionType(name: string): Type {
return {type: name, error: string}
}
type SearchFailureAction = FailureActionType("SEARCH_FAILURE")
Obviously there's problems in the way the typing/assignments are written in the return statement, but it would work such that
type SearchFailureAction = { type: "SEARCH_FAILURE", error: string }
Is there any way to do that?
You want a generic.
type FailureActionType<T: string> = { type: T, error: string }
The <T> there says that this type is dependent on another type.
<T: string> means this dependent type must be a type of string.
{ type: T, error: string } means the resulting type must have the dependant type on the type key of the object.
You use it by passing in a value for T in <> like so:
type SearchFailureAction = FailureActionType<"SEARCH_FAILURE">
const action1: SearchFailureAction = { type: 'SEARCH_FAILURE', error: 'some error' }
const action2: SearchFailureAction = { type: 'BAD', error: 'some error' } // type error
flow.org/try Proof
Generics are pretty powerful. Read the docs for more.
https://flow.org/en/docs/types/generics/
Related
I have the following in my declarations file (included in my [libs]):
export type EtlFieldNoIdxT = {
name: Name,
purpose: Purpose,
}
export type EtlFieldT = {
idx: number,
...EtlFieldNoIdxT
}
And the following in my use of the types:
export const createEtlField = (
etlFields: { [Name]: EtlFieldT },
newField: EtlFieldNoIdxT,
) => {
if (etlFields === {}) {
throw new Error({
message: 'Cannot create a new etlField with an empty etlFields',
});
}
const field: EtlFieldT = {
idx: maxId(etlFields, 'idx') + 1,
...newField,
};
const subject: Name = Object.values(etlFields).find(
(f) => f.purpose === 'subject', // <<< f.purpose "missing in mixed" error
).name; // <<< .name "missing in mixed" error
return newEtlField(field, subject);
};
Despite having annotated the input, can flow not infer the type of what Object.values would thus return?
Thank you in advance for pointing out my misunderstanding.
- E
If you check the declaration for Object.values you'll find that it returns an array of mixed:
static values(object: $NotNullOrVoid): Array<mixed>;
A quick google search came back with
https://davidwalsh.name/flow-object-values
So to solve your issue, you wrap Object.values(...) with any, and then inside your find arg you can type it as EtlFieldT and finally refine your type back to EtlFieldT after find.
const subject: Name = ((Object.values(etlFields): any).find(
(f: EtlFieldT) => f.purpose === 'subject',
): EtlFieldT).name;
Though you should be aware that find has the possibility of returning undefined. So to be sound, you should run the find, and declare subject if the value exists.
Given an object type that has functions as values I need to create object types with similar structure but having values as async functions.
Imagine this function
function convert(foo) {
return async function(...args) {
return foo(...args)
}
}
Applied to values of this type
type Source = {
foo: number => string,
bar: string => number
}
So I get this type
type Result = {
foo: number => Promise<string>,
bar: string => Promise<number>
}
Is it possible in flow?
I don't understand how Unions work.
Doc reference
see this doc example about exact Union Types
Problem
Code below will throw a flow error on item.rocks:
/* #flow */
type MoutainType = {|
rocks: boolean,
|};
type OceanType = {|
waves: boolean,
|};
type HolidayType = MoutainType | OceanType;
const haveHoliday = (item: HolidayType) => {
return item.rocks; //----------------> Error (but shouldn't)
}
Try yourself
See live demo
You have defined HolidayType to be an union of the types MountainType or HolidayType. Flow needs to be able to determine which type it is dealing with before it will allow you to access an exclusive member property without throwing an error.
If you test for the rocks property before attempting to access it, Flow will then be able to determine which type is in play.
const haveHoliday = (item: HolidayType) => {
if (typeof item.rocks !== 'undefined') {
// Flow now knows that this must be a MountainType
return item.rocks;
}
}
Look at the docs for Disjoint Unions and see if there is a literal value that you can use as the selector for type, and that gives you a more natural code path e.g.
type MountainType = {
terrain: 'rocks'
}
type OceanType = {
terrain: 'waves'
}
type HolidayType = MountainType | OceanType
const haveHoliday = (item: HolidayType): boolean => {
return item.terrain === 'rocks'
}
In this example, Flow shows us a way to read properties depending on the type.
I have refactored it to look like this, and it works as expected:
type Success = { success: true, value: boolean };
type Failed = { success: false, error: string };
type Response = Success | Failed;
function handleResponse(response: Response) {
const value = response.success && response.value;
const error = !response.success && response.error;
}
However, when the common property is a string, it works when doing a === check, but it doesn't if you cache the check into a variable:
type Success2 = { success: 'success', value: boolean };
type Failed2 = { success: 'not_success', error: string };
type Response2 = Success | Failed;
function handleResponse(response: Response2) {
const isSuccess = response.success === 'success';
// const value = response.success === 'success' && response.value; // WORK
const value = isSuccess && response.value; // DOESN'T WORK
}
In other words, there must be a === check (literally) before reading the variable, can't have it in a variable.
Is this a known Flow limitation, or am I missing something?
Yep, this is expected behavior. The feature you are referring to here is type refinement. The implementation is based on the standard flow-control mechanisms of Javascript, meaning that saving the result of a test and using it later will discard the type information that Flow might otherwise be able to infer. All Flow knows in your non-working example is that isSuccess is a boolean, it has no idea that true there implies that response.value exists.
I have this interface:
interface IFormData {
[string]: string
};
export type { IFormData };
It's simple interface that accepts key-value only string. But when I use this,
const formData:IFormData = { email: '...', password: '...' };
it gives me this error:
[flow] property $key of IFormData (Indexable signature not found in object literal)
I also tried this, but it gives me same error:
var formData: IFormData; // Error
formData['email'] = ...;
formData['password'] = ...;
I searched this on google almost 2 days, but still stuck in here and I need some help!
Any advice will very appreciate it.
If you switch from interface to type, Flow seems to be a lot happier with this sort of thing:
type IFormData = {
[string]: string
}
const formData: IFormData = { email: '...', password: '...' };
I'm guessing the fact that interfaces are nominally typed, rather than structurally typed, has something to do with this.