Flow type for a Map? - flowtype

I have a function that expects a Map as in input, how can I type this in Flow?
const myFunction = (map: Map) => {
// do stuff
}
const myMap = new Map([['key', 'value']]);
myFunction(myMap);
Feels a standard thing to do but I couldn't find out how on the Flow docs.

The Map type takes two type arguments, one for the type of its keys and one for the type of its values. In your example, they are both string.
const myFunction = (map: Map<string, string>) => {
// do stuff
}
const myMap = new Map([['key', 'value']]);
myFunction(myMap);
(playground)

Related

Trouble reading documentation Interface Definition

I'm trying to figure out how a Redux createStore function works(what parameters it accepts) with enhancers from the documentation.
what I understand is, "sayHiOnDispatch" takes a "createStore" function as a parameter and creates a closure around the inner anonymous function which accepts 3 arguments,
rootReducer, preloadedState, enhancers
finally, it return an object {...store,dispatch:newDispatch}.
what I don't understand is: 1)Where is sayHiOnDispatch is being called from?
2)How the anonymous function is getting called?
3)What variable receives the return value of return { ...store, dispatch: newDispatch }
4)What calls the newDispatch functions?
5)How can I understand the function structure(params, return values, etc..) from the Interface Definition?
export type StoreEnhancer<Ext = {}, StateExt = never> = (
next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
S = any,
A extends Action = AnyAction
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
The redux tutorial code.
export const sayHiOnDispatch = (createStore) => {
return (rootReducer, preloadedState, enhancers) => {
const store = createStore(rootReducer, preloadedState, enhancers)
function newDispatch(action) {
const result = store.dispatch(action)
console.log('Hi!')
return result
}
return { ...store, dispatch: newDispatch }
}
}
Hopefully, someone will provide a fishing-rod for me to fish the fish.
Answering the questions in order:
sayHiOnDispatch gets called either as return enhancer(createStore)(reducer, preloadedState) inside of createStore itself, or on the same line as part of a "composed" enhancer (like compose(applyMiddleware(), sayHiOnDispatch) )
The anonymous function is called on that same line - it's the return value from enhancer()
The returned {...store, dispatch: newDispatch} is the actual store variable, as in const store = createStore()
newDispatch is the actual store.dispatch function, so it's called as store.dispatch(someAction)
Yeah, that is definitely a complex type signature :) To be honest I wouldn't worry about it - odds are you won't ever end up writing an enhancer yourself.

FlowJS and function with 2 signatures and optional parameter

How do I precisely type in Flow the following function that either takes a callback and call it later with some value or takes no arguments and return a Promise for that value?
const foo = callback => {
const p = Promise.resolve(1.0);
if (callback === undefined) {
return p;
}
p.then(callback);
}
};
I tried to use an intersection type like in:
type CallbackCase = ((number) => void) => void;
type PromiseCase = () => Promise<number>;
const foo: CallbackCase & PromiseCase =
callback => {
const p = Promise.resolve(1.0);
if (callback === undefined) {
return p;
}
p.then(callback);
};
But then Flow complains (this is also so with the latest version at https://flow.org/try):
Cannot assign function to `foo` because undefined [1] is incompatible with `Promise` [2] in the return value. [incompatible-type]
So how can I fix this?
Update: The reason for this signature is that we have older code where the function was taking a callback. We would like to convert it to the promise form while still supporting older callers. During the conversion we would like to keep the types precise. In particular at the call site only the two forms should be allowed:
let a: Promise<number> = foo();
foo(callback);
Any other forms should be rejected.
You can specify argument as a function
type Callback = <T>(T) => void;
const foo =
(callback:Callback) => {
const p = Promise.resolve(1.0);
if (callback === undefined) {
return p;
}
p.then(callback);
};
seems to works well

How to convert all object properties (function) to async functions in flowtype?

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?

Flow errors when dealing with nullable types

I have working on a redux reducer with the following state:
export type WishlistState = {
+deals: ?DealCollection,
+previousWishlist: ?(Deal[]),
+currentWishlist: ?(Deal[]),
+error: ?string
};
export type DealCollection = { [number]: Deal };
export const initialState: WishlistState = {
deals: null,
previousWishlist: null,
currentWishlist: null,
error: null
};
export default function wishlistReducer(
state: WishlistState = initialState,
action: WishlistAction
): WishlistState {
switch (action.type) {
case "GET_DEALS_SUCCESS":
return { ...state, deals: action.deals };
case types.GET_WISHLIST_SUCCESS:
console.log(action);
const currentWishlist: Deal[] = action.wishlistIds.map(
// ATTENTION: THIS LINE HERE
d => state.deals[d]
);
return {
...state,
currentWishlist,
previousWishlist: null,
error: null
};
// ...other cases
default:
return state;
}
}
The line I've flagged with the comment is getting a flow error on the d in the
brackets:
Cannot get `state.deals[d]` because an index signature declaring the expected key/value type is missing in null or undefined.
This is happening because of the type annotation: deals: ?DealCollection, which is made clearer if I change the line to this:
d => state.deals && state.deals[d]
Which moves the error to state.deals; and the idea is that if state.deals is null, then the callback returns null (or undefined), which is not a acceptable return type for a map callback.
I tried this and I really thought it would work:
const currentWishlist: Deal[] = !state.deals
? []
: action.wishlistIds.map(d => state.deals[d]);
It would return something acceptable if there are no deals is null, and never get to the map call. But this puts the error back on the [d] about the index signature.
Is there any way to make Flow happy in this situation?
Flow invalidates type refinements whenever a variable may have been modified. In your case, the thought of checking !state.deals is a good start; however, Flow will invalidate the fact that state.deals must have been a DealCollection because (theoretically) you could be modifying it in your map function. See https://stackoverflow.com/a/43076553/11308639 for more information on Flow type invalidation.
In your case, you can "cache" state.deals when you have refined it as a DealCollection. For example,
type Deal = string; // can be whatever
type DealCollection = { [number]: Deal };
declare var deals: ?DealCollection; // analogous to state.deals
declare var wishlistIds: number[]; // analogous to action.wishlistIds
let currentWishlist: Deal[] = [];
if (deals !== undefined && deals !== null) {
const deals_: DealCollection = deals;
currentWishlist = wishlistIds.map(d => deals_[d]);
}
Try Flow
that way you can access deals_ without Flow invalidating the refinement.

How to type annotate "function wrappers" (function which returns a function with the same signature as it's argument)

Is there a way to properly tell flow that I'm returning a function with the same signature as the function I'm passed, but not exactly the same function ?
This is an example of a "once" wrapper which prevents a function from being called multiple times, it works but uses an any-cast internally to make flow give up, I'd like to get rid of that cast and have 100% coverage:
module.exports.once = /*::<F:Function>*/(f /*:F*/) /*:F*/ => {
let guard = false;
return ((function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
}/*:any*/) /*:F*/);
};
Okay, first things first.
Your return value can currently never match F without your casting through any because the signature of the function you're returning is not the same because it can return undefined where the original may not.
(comment syntax removed for readability)
module.exports.once = <F: Function>(f: F): F => {
let guard = false;
return ((function () { // this function returns the return value of F or void
if (guard) { return; } // returning void
guard = true;
return f.apply(null, arguments);
}: any): F);
};
But to start typing this, we're gonna need to break down that function generic a little bit.
First of all, let's not use Function as it's generally better if we don't:
However, if you need to opt-out of the type checker, and don’t want to go all the way to any, you can instead use Function. Function is unsafe and should be avoided.
Also, we're going to extract the types of the arguments and the return value so we can manipulate them independently and construct a return type. We'll call them Args and Return so they're easy to follow.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
) ((...Array<Args>) => Return | void) => { // note `Return | void`
let guard = false;
return function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
};
};
Now that we're taking into account that our new function might return void everything type checks fine. But of course, the return type of our once function will no longer match the type of the passed function.
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once(func); // error!
// Cannot assign `module.exports.once(...)` to `onceFunc` because
// undefined [1] is incompatible with string [2] in the return value.
Makes sense, right?
So, let's discuss the signature of this function. We want our return value to have the same signature as the function we pass in. Currently it doesn't because we're adding void to the signature. Do we need to? Why are we returning undefined? How can we always return the same type from our onced function? Well, one option would be to store the return value from the single call to the function and always return the stored return value for subsequent calls. This would kind of make sense because the whole point is to allow multiple calls but not perform any of the functions effects. So this way we can avoid changing the interface of the function, so we really don't need to know whether or not the function has been called before.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
): ((...Array<Args>) => Return) => {
let guard = false;
let returnValue: Return;
return function () {
if (guard) { return returnValue; }
guard = true;
returnValue = f.apply(null, arguments);
return returnValue;
};
};
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once2(func);
One good question to ask at this point would be, why do the types match even if we're not technically returning exactly F? The answer to that is because functions in flow are structurally typed. So if they have the same arguments and return value, their types match.

Resources