Flow Type: HOC with cloneElement - flowtype

I am trying to create a higher order component, Hoc, that gives its children some extra props through React.cloneElement. I have not been able to get flowtype to know that the extra props were in fact passed down.
Below is my failed attempt, which throws the error foo type cannot be found on object literal. I would like to know what I can do to fix this.
type Props = {
foo: string,
bar: string,
};
type DefaultProps = {
foo: string,
};
declare class React2$Element<Config, DP> extends React$Element{
type: _ReactClass<DP, *, Config, *>;
}
declare function Hoc<Config, DP: DefaultProps, R: React$Element<Config>>(props: {children: R}) : React2$Element<Config, DP>
function TestComponent({foo, bar}: Props){
return <div>{bar}</div>;
}
function Hoc(props){
return React.cloneElement(props.children, {foo: 'form2wr'});
}
function Test(){
return <Hoc children={<TestComponent bar='yo' />}></Hoc>;
}

I don't have an answer to this question, but I do have a workaround.
type Props = {
foo: string,
bar: string,
};
type DefaultProps = {
foo: string,
};
type WithHOCProps<X> = $Diff<X, DefaultProps>
declare function TestComponent(props: WithHOCProps<Props>) : React$Element;
function TestComponent({foo, bar}: Props){
return <div>{foo + bar}</div>;
}
function Test(){
return <TestComponent bar='yo' />;
}
Tadahhh, no errors.

Related

Also, the action creator overrides toString() so that the action type becomes its string representation

I'm learning redux-toolkit from the official docs and came across this line- Also, the action creator overrides toString() so that the action type becomes its string representation. What does it mean?
Here's the code from the docs:
const INCREMENT = 'counter/increment'
function increment(amount) {
return {
type: INCREMENT,
payload: amount
}
}
const action = increment(3)
// { type: 'counter/increment', payload: 3 }
const increment = createAction('counter/increment')
let action = increment()
// { type: 'counter/increment' }
action = increment(3)
// returns { type: 'counter/increment', payload: 3 }
console.log(increment.toString())
// 'counter/increment'
console.log(`The action type is: ${increment}`)
// 'The action type is: counter/increment'
So, for example, when I write something like
const increment = createAction("INCREMENT")
console.log(increment.toString())
It's logging INCREMENT. So is this overriding of toString()? I'm really confused.
I'm new to redux-toolkit and any help would be appreciated. Thanks.
Normally, if you call toString() on a function, it returns the literal source text that was used to define the function:
function myFunction() {
const a = 42;
console.log(a);
}
myFunction.toString()
"function myFunction() {
const a = 42;
console.log(a);
}"
However, in this case, we want someActionCreator.toString() to return the action type that will be part of the action objects it creates:
const addTodo = createAction("todos/addTodo");
console.log(addTodo("Buy milk"));
// {type: "todos/addTodo", payload: "Buy milk"}
console.log(addTodo.toString());
// "todos/addTodo"
To make this happen, createAction overrides the actual implementation of toString for these action creators:
export function createAction(type: string): any {
function actionCreator(...args: any[]) {
return { type, payload: args[0] }
}
actionCreator.toString = () => `${type}`
actionCreator.type = type
return actionCreator;
}
This is especially useful because ES6 object literal computed properties automatically try to stringify whatever values you've passed in. So, you can now use an action creator function as the key in an object, and it'll get converted to the type string:
const reducersObject = {
[addTodo]: (state, action) => state.push(action.payload)
}
console.log(reducersObject);
// { "todos/addTodo": Function}

Flowtype generic function with different parameters

I'm struggling with flowtype declaration for a generic function with different pairs of parameters.
My goal is to have a function which return an object of certain union type depending on input parameters.
I'm having a big load of messages that i want to type (for this example i'm using only two)
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
type MessageFactory<T> = (type: $PropertyType<T, 'type'>, payload: $PropertyType<T, 'payload'>) => T;
export const factory: MessageFactory<Message1> = (type, payload) => {
return {
event: 'UI',
type,
payload
}
}
factory('receive', true);
// factory('send', { foo: true, bar: "bar" });
when i change
MessageFactory<Message1>
to
MessageFactory<Message1 | Message2>
it will throw an error
Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `payload` [3] or to `type` [4]
You can ty it here
any idea how to declare this function?
or is it stupid idea and i'm going to the wrong direction?
any better solutions?
Create a GenericMessage with type parameters for your desired properties (type and payload), then have your factory return a GenericMessage:
(Try)
type GenericMessage<TYPE: string, PAYLOAD> = {
event: 'UI',
type: TYPE,
payload: PAYLOAD
}
const factory = <T: string, P>(type: T, payload: P): GenericMessage<T, P> => {
return {
event: 'UI',
type,
payload
}
}
const test1 = factory('receive', true);
const test2 = factory('send', { foo: true, bar: "bar" });
// Let's check the new type against Message1 and Message2:
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
// Type assertions
(test1: Message1);
(test2: Message2);
(test1: Message2); // Error!
If you want, you can create a MessageFactory type that returns a GenericMessage<T, P>. You can also create an EVENT type parameter if you need to control the event property on the object.
(You don't need to call it GenericMessage, I just called it that to make a distinction between your existing types and this new one)

Higher-order function returning generic function

So in this basic example (tryflow):
// basic identity function example with generic type
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function identity<T>(same: T): T {
return same;
}
// here identity acts as (Foo) => Foo
const foo2: Foo = identity(foo);
// and here it's (Bar) => Bar
const bar2: Bar = identity(bar);
My identity function, using generics, takes whatever type is given to it. As arguments are bound to it, T becomes first Foo and then Bar.
What I want is a higher-order function which returns a generic function. I can write a higher-order function which uses generics (tryflow):
type IdentityFunction = <T>(self: T) => T;
// error here
const baseId: IdentityFunction = (same) => same;
// ^ Cannot assign function to
// `baseId` because `T` [1] is
// incompatible with `T` [2] in
// the return value.
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function makeIdentity(func: IdentityFunction): IdentityFunction {
return func;
}
const identity: IdentityFunction = makeIdentity(baseId);
const foo2: Foo = identity(foo);
const bar2: Bar = identity(bar);
For me, this approach makes the most sense. I'm honestly not sure why I get this error. How can T be incompatible with itself? Is it because a type is never explicitly applied to T? It's somehow indeterminate so it just can't be used for anything? But then, isn't that the whole point of generics? Anyway, I'm sure I'm just missing some subtle point of the type system, or maybe I'm going about this the wrong way. Any help appreciated.
You need to generically type your baseID function so Flow knows what you expect as argument and return type. It seems like Flow doesn't use the type of IndentityFunction when trying to figure out what the baseId function is really doing.
(Try)
type IdentityFunction = <T>(self: T) => T;
// no more error
const baseId: IdentityFunction = <S>(same: S): S => same;
type Foo = { prop: number};
type Bar = { prop: string };
const foo: Foo = { prop: 1 };
const bar: Bar = { prop: 'a' };
function makeIdentity(func: IdentityFunction): IdentityFunction {
return func;
}
const identity: IdentityFunction = makeIdentity(baseId);
const foo2: Foo = identity(foo);
const bar2: Bar = identity(bar);
You can simplify the instantiation of baseId to:
const baseId = <S>(same: S): S => same;
And flow still understands what's going on here.
This behavior is a little confusing and I wonder if there is a good reason for it. You would think that it could take what's on the lefthand side and apply it to the function on the right (especially in simple cases like this one). Maybe it has to do with how flow sees the righthand expression? If anyone else has an idea, I'd love to hear it.
Either way, I tend to avoid declaring the type of functions on the lefthand side of declarations. Not as a rule, I just rarely want to declare the type of a function somewhere besides the function itself.

Flow Type Check: How to iterate on possibly undefined property in redux action?

I have a Redux reducer for preferences and I am using Flow Type Checker. My reducer can take two types of actions. One for loading in all of the preferences which happens at initial app load. The second action type happens when the user updates a specific preference. Here is the code for my reducer. Where I run into problems is when I try to do action.prefs.forEach at which point flow throws an error saying ...'prefs': Property not found in 'object type'
// #flow
import {
UPDATE_PREF,
LOAD_PREFS_SUCCESS
} from '../actions/prefs';
export type actionType = {
+type: string,
prefs: Array<{_id: string, value: any}>
} | {
+type: string,
id: string,
value: any
};
export default (state: stateType = {}, action: actionType) => {
switch (action.type) {
case LOAD_PREFS_SUCCESS: {
const newState = {};
action.prefs.forEach(p => {
newState[p._id] = p.value;
});
return newState;
}
case UPDATE_PREF: {
return { ...state, [action.id]: action.value };
}
default:
return state;
}
};
As you can see I have two types of actions. When loading all of the preferences, the action has an array of preferences. [ { _id: 'color', value: 'blue' } ] And when updating a single preference, I get an id and a value. So give my two action types that have different properties, how do I get flow to not throw errors about this variation in action flow types?
In order to tell Flow which type to choose in the disjoint union, +type must be a value instead of string. Change your actionType to use values:
// #flow
import {
UPDATE_PREF,
LOAD_PREFS_SUCCESS
} from '../actions/prefs';
export type actionType = {
+type: LOAD_PREFS_SUCCESS, // not just `string`
prefs: Array<{_id: string, value: any}>
} | {
+type: UPDATE_PREF, // not just `string`
id: string,
value: any
};
Thanks to some guidance from #ross-allen and some playing around on flow.org I have found a working solution.
The short of it is that in addition to Ross' answer of adding +type: UPDATE_PREF, I also needed add typeof. So the working actionType is:
export type actionType = {
+type: typeof LOAD_PREFS_SUCCESS,
prefs: Array<{_id: string, value: any}>
} | {
+type: typeof UPDATE_PREF,
id: string,
value: any
};
Thanks again #ross-allen.

How to flowtype cover this code in a function with dereferenced object fields

I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)

Resources