Flow is telling me that an explicitly passed-in string is incompatible with null, and it seems to have something to do with an exploded object also being passed in.
I have the following in my react bootstrap library interface definition file:
declare export type FormControlProps = {|
componentClass?: ?componentClass,
// componentClass is an enum of strings 'select' | 'div' etc
// There are other params here, too.
|}
And the following in a component:
import { FormControl, type FormControlProps } from 'react-bootstrap';
type EnumSelectProps = {|
defaultText: string,
...FormControlProps,
|};
// and in the render method:
const { defaultText, ...other: FormControlProps } = this.props;
<FormControl
{...other}
componentClass="select"
value={this.state.value}
onChange={event => this.onChange(event.target.value)}
>
{ children }
</FormControl>
This seems to be fine, right? ...other has the type FormControlProps. However, I get the complaint:
v-----------
43: <FormControl
44: {...other}
45: componentClass="select"
...:
48: >
^ props of React element `FormControl`
45: componentClass="select"
^^^^^^^^ string. This type is incompatible with
463: componentClass?: ?componentClass,
^^^^^^^^^^^^^^^ null. See lib: flow-typed/npm/react-bootstrap_v0.x.x.js:463
What gives? If I cast other as any (i.e....(other: any)), it works. Also, if I make componentClass only left-hand-side optional componentClass?: componentClass, it works. (That's not the correct definition, though.) Is there any less-hacky way to handle this? Thank you!
This should work in flow v0.56.0 as you can see on flow.org/try 🙂
Related
Updated Question
I want to define a function named bsearch() to do binary searches against arrays of arbitrary object types. When I invoke the function, I want it to check whether or not the Type of the array contains a compare() method and use it, if it does. If it does not, I want it to fall back to using < and === (so it will work with strings and numbers).
What should the function declaration look like? (I don't need an actual implementation, just the syntax for a type-safe solution.)
Or maybe I'm going about this all wrong? How can I create a function that uses a method built into a parameter type if it exists, or use some other function when it doesn't?
Original Question
This is the original question, but I've replaced it with the above as it seems this wasn't getting my point across.
I want to define a function named bsearch() to do binary searches against arrays of arbitrary object types. So I'd like to do something like this:
type Comparator = <Type>(a: Type, b: Type) => -1 | 0 | 1;
static bsearch<Type extends { compare?: Comparator }>(
ary: Type[],
value: Type
): number { ... }
My goal is to specify that Type must extend a type that may or may not include the compare method. In my function, I will check whether the compare method exists on the value parameter and call if it does, or use a generic function (that uses < and ===) if it does not.
The definition of bsearch() does not produce any warnings or errors, but attempts to invoke it from my unit test does:
class Person {
name: string;
length: number;
compare: Comparator<Person>; // What goes here?
}
describe('Utils tests', () => {
const arrayOfInt = [10, 20, 30, 40];
const arrayOfStr = ['Alfred', 'Bob', 'Chuck'];
const arrayOfPersons: Person = [
{name:'Barney',length:2},
{name:'Fred',length:6}
{name:'Wilma',length:12},
];
it('can find integer in an array of integers', () => {
let search_for = 30;
let result = Utils.bsearch(arrayOfInt, search_for)
expect(result).to.be.equal(2);
});
it('can find string in an array of strings', () => {
let search_for = 'Bob';
let result = Utils.bsearch(arrayOfStr, search_for)
expect(result).to.be.equal(1);
});
it('can find Person in an array of Persons', () => {
// This one uses Person.compare() to do the search.
// The previous two tests used the fallback technique.
let search_for = {name:'Fred',length:6};
let result = Utils.bsearch(arrayOfPersons, search_for)
expect(result).to.be.equal(1);
});
});
The error message is:
TS2345: Argument of type 'number[]' is not assignable to parameter of type '{ compare?: Comparator | undefined; }[]'. Â Â Type 'number' has no properties in common with type '{ compare?: Comparator | undefined; }'.
I would appreciate pointers to other techniques if there is a better way to accomplish this (I'm still a TypeScript newbie).
Your generic is:
Type extends { compare?: Comparator }
Which means that Type must fulfill { compare?: Comparator } type. While passing object value, for example { name: 'Barney', length: 2, comparator: /* snip */}, is obviously correct, it's not the case for primitives like 10 and Bob. You need to include information about primitive types in the generic, for example:
Type extends ({ compare?: Comparator }) | number | string
Also, you'd probably want to enrich a bit the object typing:
{[key: string]: unknown, compare?: () => void } | number | string
Because, based on your description, you'd also want to accept also objects that do not have compare function in their type signature at all. If it does sound strange, I recommend reading about excess property checking.
Flow reports that a default argument value of {} is incompatible with a generic of type {}.
Here is a minimal example:
function copy<T: {}>(o: T = {}): T {
// ^ object literal [1] is incompatible with `T` [2].
return {
...o
};
}
The docs say about the type {}:
Sometimes it is useful to write types that accept arbitrary objects,
for those you should write {}.
So in what sense are they incompatible?
T: {} means "T is any subtype of {}", or in other words, T is an object type. If it has any required properties, {} is not a value of type T.
I have a union type like this:
type ActionTypes = "ACTION_ONE" | "ACTION_TWO" | "ACTION_THREE"
And now I wonder if I can type that variable will be a string but none of the above?
for example:
const myStr: ActionTypes = "something" // no error
const myStr2: ActionTypes = "ACTION_ONE" // error
tl;dr: Maybe with type assertions, but it's hard to use effectively
I don't think there's a straightforward/possible way to exclude string literals from the string type. You might consider doing a type assertion of the variable by (ab)using $Call<F, T>, but this technique is almost certainly a bad idea:
(Try)
type ActionTypes = "ACTION_ONE" | "ACTION_TWO" | "ACTION_THREE"
type NonActionFuncType<T> =
(<T: ActionTypes>(T) => false) & (<T: string>(T) => true);
const good = "blah";
(true: $Call<NonActionFuncType<typeof good>, typeof good>) // Passes
const bad: "ACTION_ONE" = "ACTION_ONE";
(true: $Call<NonActionFuncType<typeof bad>, typeof bad>) // Fails
Pragmatically, I would suggest you look for another way to do whatever you're looking to do. Flow automatically types all string literals as string unless you specify the type, so this sort of technique won't catch too many bugs (unless you're passing variables with a literal string type, which you might be doing).
The two possibilities of DateObj seems disjoint to me still, Flow doesn't seem to refine the type on the else branch. How can I make this correctly typed?
type DateObj = {| date: string |} | {| dateTime: string |}
export const parseDate = (dateObj: DateObj) => {
if (dateObj.date) {
return moment(dateObj.date).toDate()
} else {
return moment(dateObj.dateTime).toDate()
}
}
Flow: property dateTime. Property not found in object type
(at the line of the last return statement)
Trying in https://flow.org/try/ helped, it gave me an additional error
if (dateObj.date) {
^ Sketchy null check. Perhaps you meant to check for null instead of for existence?
Fixing this error also solved my original issue
if (dateObj.date !== undefined) {
This is how you annotate a variable / constant as holding a function of a particular type:
declare type TFunction = () => any;
const foo: TFunction = function foo() {};
What is the syntax when one declares a function:
function boo() {}
?
There's no way to put: TFunction on your function boo() declaration. However, you can flow check it by writing the no-op statement (boo: TFunction); afterward. The only drawback is this evaluates boo at runtime.
Probably the best way to do it though is to not worry about explicitly declaring that boo is a TFunction, and instead just rely on Flow to check it any time you use boo where a TFunction is expected.
Here's a more concrete example of what I mean: (Try flow link)
/* #flow */
type ArithmeticOperation = (a: number, b: number) => number;
function add(a: number, b: number): number {
return a + b;
}
function concat(a: string, b: string): string {
return a + b;
}
function fold(array: Array<number>, operation: ArithmeticOperation): number {
return array.reduce(operation);
}
fold([1, 2, 3], add); // ok because add matches ArithmeticOperation
fold([1, 2, 3], concat); // flow error because concat doesn't match
Coming late to this question, but if you are talking about declarations in the sense of "compile-time declarations, separate from code, that use the declare keyword", then per the Flow declarations docs, you should be able to declare globals like this:
declare var boo: TFunction;
or scoped items as members of their containing module or type:
declare class FunctionHolder {
boo: TFunction;
}