I want my type to validate "default props" passed to the React component but $Diff (which is designed for this) is not doing so. How can I get this behavior?
/* #flow */
type Props = { name: string, age: number };
type DefaultProps = { age: number };
type RequiredProps = $Diff<Props, DefaultProps>;
const a1: RequiredProps = { name: 'foo' };
const a2: RequiredProps = { name: 'foo', age: 1 };
const a3: RequiredProps = { name: 'foo', age: '1' }; // why no error?
$Diff<A, B> returns an object type that, by default, will accept additional properties. This means, that any properties that are on A and B can be on an object of type $Diff<A, B> with any type. In your case, RequiredProps is equivalent to { name: string }. Thus, an object { name: '', age: <whatever> }: RequiredProps is completely valid for any value of <whatever>.
It seems like what you really want is an object type that requires all of the properties of RequiredProps and requires that any property from DefaultProps matches that type definition. This can be achieved with the type,
type RequiredAndDefaultProps = { ...$Exact<RequiredProps>, ...DefaultProps };
which can be verified
type Props = { name: string, age: number };
type DefaultProps = { age: number };
type RequiredProps = $Diff<Props, DefaultProps>;
type RequiredAndDefaultProps = { ...$Exact<RequiredProps>, ...DefaultProps };
({ name: 'foo' }: RequiredAndDefaultProps);
({ name: 'foo', age: 42 }: RequiredAndDefaultProps);
({ name: 'foo', age: '42' }: RequiredAndDefaultProps); // Error: age should be number
({ age: 42 }: RequiredAndDefaultProps); // Error: missing name
({ name: 'foo', bar: 'bar' }: RequiredAndDefaultProps);
Try Flow.
Related
I'm wondering if there is a pattern that allows you to use action creators inside of other action creators. The modifyMassProperty action creator lets you pass any number of actions which are then iterated over and dispatched accordingly. I would very much like to be able to use this method in the getOrbitalBurn action creator since it would be semantically more appealing than using the dispatch method made available by the thunk three times in a row. I'm confident I must either have missed something, or that I'm guilty of getting tangled up in some sort of anti pattern that I concocted during one of my lesser days.
export const modifyMassProperty = (
...massProperties: MassProperty[]
): ThunkAction<void, AppState, void, Action> => (
dispatch: Dispatch<ScenarioActionTypes>
) =>
massProperties.forEach(massProperty =>
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: massProperty
})
);
export const getOrbitalBurn = (
payload: { primary: string; periapsis: number; apoapsis: number },
applyBurn = true
): ThunkAction<void, AppState, void, Action> => (
dispatch: Dispatch<ScenarioActionTypes>,
getState: any
) => {
const scenario = getState().scenario;
const primary = getObjFromArrByKeyValuePair(
scenario.masses,
'name',
payload.primary
);
const orbit = orbitalInsertion(primary, payload, scenario.g);
if (applyBurn) {
const [spacecraft] = scenario.masses;
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: {
name: spacecraft.name,
key: 'vx',
value: orbit.x
}
});
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: {
name: spacecraft.name,
key: 'vy',
value: orbit.y
}
});
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: {
name: spacecraft.name,
key: 'vz',
value: orbit.z
}
});
}
dispatch({
type: MODIFY_SCENARIO_PROPERTY,
payload: {
key: 'orbitalInsertionV',
value: { x: orbit.x, y: orbit.y, z: orbit.z }
}
});
};
Using the React Context API, I've built this reducer:
export const usersReducer = (state: UsersState, action: UsersAction) => {
switch (action.type) {
case TOGGLE_MODAL: {
return {
...state,
isModalOpen: !state.isModalOpen
};
}
case CANCEL_REQUEST: {
return {
...state,
isCancelRequest: action.payload
};
}
case UPDATE_COMPANY: {
return {
...state,
companyId: action.payload
};
}
default: {
return state;
}
}
}
The associated Actions look like this:
// Note: `ActionType` = `string`
export const TOGGLE_MODAL: ActionType = 'TOGGLE_MODAL';
export const CANCEL_REQUEST: ActionType = 'CANCEL_REQUEST';
export const UPDATE_COMPANY: ActionType = 'UPDATE_COMPANY';
type ToggleModalAction = {type: typeof TOGGLE_MODAL};
type CancelRequestAction = {type: typeof CANCEL_REQUEST, payload: boolean};
type UpdateCompanyAction = {type: typeof UPDATE_COMPANY, payload: number};
export type UsersAction =
| ToggleModalAction
| CancelRequestAction
| UpdateCompanyAction;
On the two action.payload instances, Flow is saying this:
Cannot get `action.payload` because property `payload` is missing in `ToggleModalAction`
I thought the way I defined my 3 "...Action" types, I could include payload where warranted and exclude it where it's not needed, like in ToggleModalAction.
Any ideas how to solve this?
By doing typeof TOGGLE_MODAL, etc., the type key of your UsersAction type will always be string. What you need in order to get type help from Flow's disjoint unions is the string literals themselves:
type ToggleModalAction = {type: 'TOGGLE_MODAL'};
type CancelRequestAction = {type: 'CANCEL_REQUEST', payload: boolean};
type UpdateCompanyAction = {type: 'UPDATE_COMPANY', payload: number};
I have a seemingly simple question about Flow.
Given the following objects:
const input: InputObject = {
key1: {
prop1: 'value1',
},
key2: {
prop2: 'value2',
prop3: false,
},
};
const output: OutputObject = {
prop1: 'value1',
prop2: 'value2',
prop3: false,
};
How can I get the type of OutputObject, based on InputObject?
type OutputObject = ??magic?? InputObject ??magic??;
Context
I want to type a React HOC component which will inject some properties based on a configuration object, like:
type Props = {
prop1: string,
prop2: string,
prop3: boolean,
};
class Component extends React.Component<Props> { /* */ }
Hoc({
key1: () => ({
prop1: 'value1',
}),
key2: () => ({
prop2: 'value2',
prop3: false,
}),
})(Component);
I already know how to correctly type the HOC to get it type-safe and how to get InputObject from the configuration object passed to the HOC, but I'm stuck at how to "flatten" the InputObject type to OutputObject type, so that <Component /> will be happy because it will be getting all the required properties from the HOC :)
Even more important! I want the following to be a type error:
const HocComponent = Hoc({
key1: () => ({
prop1: 'value1',
}),
key2: () => ({
prop2: 'value2',
}),
})(Component);
<HocComponent /> // Error! prop3 is required
<HocComponent prop3={true} /> // Happy again
Thank you!
Using Flowtype together with Redux, I have a type like this:
export type MapState = {
addresses: Address[],
selected: Array<number>
}
and an action creator:
export const setParams = (params: any): Action => {
return { type: actionTypes.SET_PARAMS, payload: { params };
}
In the reducer, I merge the params into the state:
export default (state: MapState = initialState, action: SetParamsAction) => {
switch (action.type) {
case actionTypes.SET_PARAMS: {
return {
...state,
...action.payload.params
}
[...]
I'm looking for a possibility to tell Flowtype to accept params in the action creator, if it is an object consisting only of properties of MapState, so that I can get rid of the any in setParams. Any idea?
You can just add a exact PossibleParams Object type like so:
type PossibleParams = {|
addresses?: Address[],
selected?: number[],
|};
export const setParams = (params: PossibleParams): Action => ({
type: actionTypes.SET_PARAMS,
payload: {
params,
},
});
You can check all the possibilities on flow.org/try 🙂
I activated flow in my project with redux, but the Action declarations doesn't work as I expected.
Declarations are:
type PostRequest = {
type: string;
};
type PostPayload = {
posts: Object;
offset: number;
hasMore: boolean;
};
type PostSuccess = {
type: string;
payload: PostPayload;
};
type PostError = {
type: string;
};
type PostSelected = {
type: string;
postId: string;
};
export type Action = PostSuccess | PostError | PostSelected | PostRequest;
In actionCreators I don't see any errors, while reducer I get this error in the use of property "payload": property 'payload' (Property not found in object type).
This is my reducer:
import type { Action } from "../types";
// other import...
export default function(state: State = initialState, action: Action):
State {
switch (action.type) {
// ...
case POST_SUCCESS: {
const { posts, offset, hasMore } = action.payload;
return {
...state,
isFetching: false,
posts: _.merge(state.posts, posts),
offset: state.offset + offset,
hasMore,
};
}
// ...
What is the correct way to declare Actions?
You can define Disjoint Unions
type ActionA = {
type: 'A',
a: number
};
type ActionB = {
type: 'B',
b: string
};
type Action = ActionA | ActionB;
type State = number;
function reducer(state: State, action: Action): State {
switch(action.type) {
case 'A' :
return action.a
case 'B' :
return action.b.length
default :
(action: null) // check for exhaustivity
throw `unknown action`
}
}