flowtype - higher order functions - currying - flowtype

type hof = (f:Function) => Function;
type ty_MiddleWare = (store:StoreType) => hof;
How can I type higher order functions ? A function that returns a function that returns a function ?
I am trying to make this code typesafe and the above attempt is not accepted by flow. The problem comes up when using curryed functions.
this is how I indend to use the ty_MiddleWare:
const addLoggingToDispatch = (store) :ty_MiddleWare=>{
return (next:Function)=>{
return (action)=>{
console.group(action.type);
console.log('%c prev state','color: gray', store.getState());
console.log('%c action','color: blue',action);
const returnValue=next(action);
console.log('%c next state','color: green',store.getState());
console.groupEnd();
return returnValue;
};
};
};

Don't you mean:
const addLoggingToDispatch:ty_MiddleWare = (store) => { ?
The way you wrote it, addLoggingToDispatch returns your ty_MiddleWare type. In my way, you declare addLoggingToDispatch to be ty_MiddleWare

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

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.

React-Reduc's connect does not pass anything back to my component

So I'm a little stumped with React-Redux's connect, in that it seems to not pass anything back to my component. I've been trying to debug this all morning now, and I've looked at a few tutorials but I can't see what I've done wrong. Here's a snippet of the relevant code below. Am I misunderstanding connect()?
const TodoList = (props) => {
console.log(props); // Returns only an object with one property
// dispatch which holds a function. Where
// are my todos, filter, toggleTodo & removeTodo?
return (
<li> placeholder </li>
)
}
const RenderTodoGenerated = connect(
todoStateToProps,
todoDispatchToProps
)(TodoList);
const todoStateToProps = (state) => {
return {
todos: state.todos,
filter: state.filter
}
}
const todoDispatchToProps = (dispatch) => {
return {
toggleTodo: (toggleId) => {
dispatch(toggleTodo(toggleId));
},
removeTodo: (removeId) => {
dispatch(removeTodo(removeId));
}
}
}
In ES6, variables declared with const do not exist until the line they're declared on. Your call to connect occurs before todoStateToProps and todoDispatchToProps actually exist, so at that point they're undefined. You need to move the call to connect to the end of that chunk of code.
Also, as an FYI, connect supports an "object shorthand" for handling the mapDispatch argument. Instead of writing an actual function as you have in that example, you can simply pass an object full of action creators as the second argument, and they will be wrapped up with dispatch. So, all you need is:
const actionCreators = {toggleTodo, removeTodo};
const RenderTodoGenerated = connect(todoStateToProps, actionCreators)(TodoList);

Resources