Flow finds return type of functions very well, can we use that type in other declarations in any way?
Example of what I would like to achieve:
const mapStateToProps = (state: ReduxState) => ({
val1: selector1(state),
val2: selector2(state),
})
type Props = typeof mapStateToProps()
class Home extends Component {
props: Props;
render () {
return <div />
}
}
const connector = connect(mapStateToProps);
export default connector(Home);
There are a couple of hacky ways to do this:
type Return_<R, Fn: () => R> = R;
type Return<T> = Return_<*, T>;
function foo() {
return 1;
}
const x: Return<typeof foo> = ''; // error
function foo() {
return 1;
}
/*::
const __result = foo();
*/
const x: typeof __result = '';
@vkurchatkin Awesome example, I am now wondering why does the same trick not work for the function arguments:
type Return_<R, F: (...args: Array<any>) => R> = R;
type Return<T> = Return_<*, T>;
type Arguments_<A, F: (...args: A) => any> = A;
type Arguments<F> = Arguments_<*, F>;
function foo(n: number) {
return n;
}
const r: Return<typeof foo> = ''; // error
const a: Arguments<typeof foo> = ['']; // no error :(
https://flow.org/try/#0C4TwDgpgBAShwFcBOA7A+gHhgGigMQC4oAKAOnIEMkBzAZyIEEkkKQMKUQA+ASigF4usIf1gBuAFChIseMhQYAKiNmJUmAFS5lkqeGhNqCALYQUwWpga5CJcqSp1GfQVA7cBUBpOkGaJswsMPBVDAPNLDC18Ll0AMwQUAGNgAEsAexQoOPT04hQiFBMAIwgkPgBvCSgoJDlUKBRJAF8JCSTM2mBaojg1BV90uOzclQByMbEoAHppqDKkdKR2zu6KRn9TCIxB4Zz0lQBtCYBdKdnG9PnmJagCYiA
That's because type of arguments flows in the opposite direction. *
captures whatever flows in, not what flows out. Simple example:
var x: * = 1; // number ~> *
declare var y: typeof x;
(y: string); // error
declare function fn(x: number): void;
declare var z: *;
fn(z); // * ~> number
declare var u: typeof z;
(u: string); // no error
The same happens when function flows into another function, like in your example.
@vkurchatkin Do you think your Return
utility type could be added to the language? Seems like a useful thing
Not Return
specifically, but Apply
makes sense to me
@nickmessing is this working for u? Do u have a repo example of this?
@sibelius, yes, this helper works fine:
type Return_<R, F: (...args: Array<any>) => R> = R;
type Return<T> = Return_<*, T>;
// https://hackernoon.com/redux-flow-type-getting-the-maximum-benefit-from-the-fewest-key-strokes-5c006c54ec87
// https://github.com/facebook/flow/issues/4002
// eslint-disable-next-line no-unused-vars
type _ExtractReturn<B, F: (...args: any[]) => B> = B;
export type ExtractReturn<F> = _ExtractReturn<*, F>;
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({
actions: {
logout: () => dispatch(logout()),
},
});
type ReduxProps = ExtractReturn<typeof mapStateToProps>;
type ReduxActions = ExtractReturn<typeof mapDispatchToProps>;
type Props = {} & ReduxProps & ReduxState
class EntriaComp extends Component<Props> {}
I think we can use $Call
to improve this as well
cc @calebmer
can we close this?
Can ExtractReturn be added to the global Flow helpers as $Return?
you can use $Call instead of ExtractReturn
Proposed solutions do not work for my case: Flow Try
I need to explicitly type returns of actionCreators
methods to make these work: Flow Try
The dream is using $ObjMap
to completely avoid duplication:
const actionCreators = {
// ...
};
export type Action = $Values<$ObjMap<typeof actionCreators, ExtractReturnType>>;
Edit: I think my problem is that the inferred return types not being literal. I guess what I need from Flow is return type of a function like () => 'hey'
being inferred as 'hey'
instead of string
.
Hi everyone,
@vkurchatkin, so based on what you wrote on this comment, I assume there is no way to extract function arguments, or is any?
I can't get it to work either. Here's a very simple example.
Does not throw:
type Props = ReduxProps & { user: string };
this.props.user.foo();
Does throw:
type Props = { user: string };
this.props.user.foo();
When using ExtractReturn
helper, user
type being "string" should be properly propagated from global state all the way to this.props
. But it doesn't.
UPDATE
Finally got it to work using spread and exact helper!
type Props = {|
...ReduxProps,
customProp: customType
|}
This probably can be closed.
/cc @mrkev
Hi @kangax,
I've just seen your comment, would you mind to share ReduxProps
type?
I'm asking you this as I believe the error you were having was caused by the fact that when intersecting types (using &) if any of the them share a property, the resulting type would hold on that property an union of all the values for it, does it make sense? was your error caused by this?
this is the version using $Call
, it is much faster than using *
export type ExtractReturn<Fn> = $Call<<T>((...Iterable<any>) => T) => T, Fn>;
Example of extracting ReduxProps:
type ReduxProps = ExtractReturn<typeof mapStateToProps>;
https://flow.org/try/#0C4TwDgpgBAogHsATgQwMbAEoWAV0QOwB4AxfAPigF4oASAYWQBtHDCAVMgCk4Do+BJYBBQAjRhELJ8IMgEoqFNvMqKANFFJkA3ACgoO0JChYAJjjgBlYMiFUoAbz1RUALigBnJAEt8Ac1U6AL56OgBmOPjoXgD2+FDInJ42EG6m5lbJ8o5QUIjYeHHZOVAibklCPKgBOcHBYRFRsSWcYMgoALbubvBIaJj5BISGENGh8XIOTi1tyJ08pR7efrK6OdMd7vNuItHR4lIrQUA
can we close this?
Sure 馃憤
I'm having some issues with typing mapDispatchToProps
as an object of actions:
const mapActionsToProps = {
logout
}
connect(null, mapActionsToProps)(Comp)
Since it's a simple object of functions, I should be able to use typeof
, right?
type ReduxActions = typeof mapActionsToProps
type Props = {} & ReduxActions // or type Props = { ...ReduxActions }
class Comp extends Component<Props> {}
But it doesn't work, Flow loses the inferred type of the actions and see them as any
.
I reported the bug (?) here: https://github.com/facebook/flow/issues/6797
Does someone have a workaround? Or am I doing something wrong? (cc @sibelius )
Edit: so it appears only when the actions are imported from another file. 馃く
Hello. I am trying to extract union type of "foo": type Foo = "a" | "b" | "c"
but I could get only type Foo = "string"|"string"|"string"
const getObject = () => {
const r = {
a: {foo: "a"},
b: {foo: "b"},
c: {foo: "c"}
}
return r;
}
type ResultType = $Call<typeof getObject>;
type Values = $Values<ResultType>
type AUnion = $PropertyType<Values, "foo"> // should be union of literals, but it's union of strings
const a: AUnion = "a"
const b: AUnion = "b"
//should be an error here
const fooBar: AUnion = "bar"
https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAodBjOA7AzgFzAHMBTfAeQCMArEjQgXjAAoBKMBgPjAG9UwwWPIQBOHXvwFSAhgC5eUOHHkAiaSoC+AGklSwleT0XKwKypp16BGQ8dUZNuqcGBhpYeIThQwcfAAsSMQBrEgBPXDAEAEsAhSVVAhFo7CIPWKDpGE0wADcsgFcSSQ1JETICkWwwEQBuflLUfDCABxIwACUSXAKYfAAVVvamABIAYSyYAB5mtu9iMipaek562faANULu8RGtmCLcKa6evsG2ziahsABBAFVsaJxdgAURODaRZvOSKf3DrSmYwqbguMC4fxwXoAE307QKj2e8xgGSqWVwgMoBUIsQA5JEEU9qvMkikiLhJKghAQ3PJ7ojqkw1CoqTgaQZbg8ieIzCzUC4IVCYLDKO1ogBbFpwXDRSgwYrUwjGABC0hEdK5zyZxkoapZQA
Hello, can't figure out how to use $Call<typeof myfunc>
, in case myfunc returns promise.
Smth like:
function asd(): Promise<number> {
return Promise.resolve(3);
}
(async () => {
const num: $Call<typeof asd> = await asd();
})()
Getting error:
7: const num: $Call<typeof asd> = await asd();
^ Cannot assign `await asd()` to `num` because number [1] is incompatible with `Promise` [2].
References:
1: function asd(): Promise<number> {
^ [1]
1: function asd(): Promise<number> {
^ [2]
Promise chain also not working as expected:
function asd(): Promise<number> {
return Promise.resolve(3);
}
asd().then((num: $Call<typeof asd>) => {});
Getting error:
5: asd().then((num: $Call<typeof asd>) => {});
^ Cannot call `asd().then` because `Promise` [1] is incompatible with number [2] in the first argument.
References:
1: function asd(): Promise<number> {
^ [1]
1: function asd(): Promise<number> {
^ [2]
@alexandrricov
function asd(): Promise<number> {
return Promise.resolve(3);
}
(async () => {
const num: $Call<typeof $await, $Call<typeof asd>> = await asd();
})()
Most helpful comment
There are a couple of hacky ways to do this: