I try to type redux action creators. I have action types like:
// #flow
export const SET_USER_TOKEN = "SET_USER_TOKEN";
export const SET_TOKEN_IN_COOKIES = "SET_TOKEN_IN_COOKIES";
Now I want to create a type of action creator:
import * as actions from "./actions";
type SetUserTokenAction = {
type: actions.SET_USER_TOKEN,
token: string
};
The problem is with type property, I can not do it like above. Do I have to use literal string?
I know it is possible to do something similar for a whole object:
type ActionTypes = $Keys<typeof user>;
I would like to do the same for the value of a simple string variable.
You can use typeof oprator:
type SetUserTokenAction = {
type: typeof actions.SET_USER_TOKEN,
token: string
};
The only caveat is to type the const as well (otherwise its type resolved as string):
export const SET_USER_TOKEN: 'SET_USER_TOKEN' = 'SET_USER_TOKEN';
Related
I have the following in my declarations file (included in my [libs]):
export type EtlFieldNoIdxT = {
name: Name,
purpose: Purpose,
}
export type EtlFieldT = {
idx: number,
...EtlFieldNoIdxT
}
And the following in my use of the types:
export const createEtlField = (
etlFields: { [Name]: EtlFieldT },
newField: EtlFieldNoIdxT,
) => {
if (etlFields === {}) {
throw new Error({
message: 'Cannot create a new etlField with an empty etlFields',
});
}
const field: EtlFieldT = {
idx: maxId(etlFields, 'idx') + 1,
...newField,
};
const subject: Name = Object.values(etlFields).find(
(f) => f.purpose === 'subject', // <<< f.purpose "missing in mixed" error
).name; // <<< .name "missing in mixed" error
return newEtlField(field, subject);
};
Despite having annotated the input, can flow not infer the type of what Object.values would thus return?
Thank you in advance for pointing out my misunderstanding.
- E
If you check the declaration for Object.values you'll find that it returns an array of mixed:
static values(object: $NotNullOrVoid): Array<mixed>;
A quick google search came back with
https://davidwalsh.name/flow-object-values
So to solve your issue, you wrap Object.values(...) with any, and then inside your find arg you can type it as EtlFieldT and finally refine your type back to EtlFieldT after find.
const subject: Name = ((Object.values(etlFields): any).find(
(f: EtlFieldT) => f.purpose === 'subject',
): EtlFieldT).name;
Though you should be aware that find has the possibility of returning undefined. So to be sound, you should run the find, and declare subject if the value exists.
Is there any way I can do something like this:
// #flow
function FailureActionType(name: string): Type {
return {type: name, error: string}
}
type SearchFailureAction = FailureActionType("SEARCH_FAILURE")
Obviously there's problems in the way the typing/assignments are written in the return statement, but it would work such that
type SearchFailureAction = { type: "SEARCH_FAILURE", error: string }
Is there any way to do that?
You want a generic.
type FailureActionType<T: string> = { type: T, error: string }
The <T> there says that this type is dependent on another type.
<T: string> means this dependent type must be a type of string.
{ type: T, error: string } means the resulting type must have the dependant type on the type key of the object.
You use it by passing in a value for T in <> like so:
type SearchFailureAction = FailureActionType<"SEARCH_FAILURE">
const action1: SearchFailureAction = { type: 'SEARCH_FAILURE', error: 'some error' }
const action2: SearchFailureAction = { type: 'BAD', error: 'some error' } // type error
flow.org/try Proof
Generics are pretty powerful. Read the docs for more.
https://flow.org/en/docs/types/generics/
I am wondering how can I return object of the same type as reducer function:
function storeReducer(
state = INITIAL_APPLICATION_STATE,
action: Actions
): ApplicationState {
switch (action.type) {
case LOAD_USER_THREADS_ACTION:
return handleLoadUserThreadsAction(state, action);
default:
return state;
}
}
I expect object of type ApplicationState, but with that approach:
StoreModule.forRoot({storeReducer})
I am getting object with key:
storeReducer:{ // object of type Application State}
I am expecting to get object (without additional storeReducer key):
{//object of type Application State}
Tried also StoreModule.forRoot(storeReducer) but then I am getting empty objects and it is not working.
The forRoot method on StoreModule expects and ActionReducerMap, not the result of your reducer.
I typically set mine up in a seperate file like this:
export interface IAppState {
aPieceOfState: IAPieceOfState;
}
export const reducers: ActionReducerMap<IAppState> = {
aPieceOfState: aPieceOfStateReducer
};
Then import this to app.module.ts and use it like:
StoreModule.forRoot(reducers)
Or you can put an assertion StoreModule.forRoot({storeReducer} as ActionReducerMap<IAppState>)
I have a reducer for storing preferences. It has two action types. One for loading in all preferences from database and another for updating a single preference. I have a working standalone example but it breaks once used inside of my app.
The issue is that my preferences reducer only handles two types of actions, while my app has multiple reducers that fire other actions. A solution to get the code running is to add a third general type for actions not related to this reducer. That however creates Property not found in 'object type'. errors when I try to access properties of the action.
Working flow example
// #flow
const LOAD_PREFS_SUCCESS = 'LOAD_PREFS_SUCCESS';
const UPDATE_PREF = 'UPDATE_PREF';
type aType = {
+type: string
};
export type actionType = {
+type: typeof LOAD_PREFS_SUCCESS,
prefs: Array<{_id: string, value: any}>
} | {
+type: typeof UPDATE_PREF,
id: string,
value: any
};
export default (state: {} = {}, action: actionType) => {
if (action.type === LOAD_PREFS_SUCCESS) {
action.prefs.forEach(p => {
console.log(p);
});
}
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;
}
};
This is valid flow but when the app actually runs, I get an error when an action with type INIT_APP or something runs. The error says action must be one of: and then it lists the two types I have in actionType as the expected and an actual of { type: string }.
I can get the app running by adding a third type to actionType like this:
export type actionType = {
+type: typeof LOAD_PREFS_SUCCESS,
prefs: Array<{_id: string, value: any}>
} | {
+type: typeof UPDATE_PREF,
id: string,
value: any
} | {
+type: string
};
Even though the app now runs without error, it does not pass flow type check. Throwing errors of Property not found in object type. Here is an example on flow.org
Since every reducer ends up seeing every action, you'll want the type of this reducer function to include all the possible actions in your app. I usually define a single variant actionType with everything available in the app and use that in every reducer.
The reason why your last code example doesn't work is because the third, anonymous action type {type: string} is too vague. Before this, Flow could look at the two options in the action, and see that it would know which one was which based on the case statements. But with the third action type, an action like {type: "LOAD_PREFS_SUCCESS"} would match the third case in the type. So testing action.type === LOAD_PREFS_SUCCESS is no longer enough to prove that the action will have a prefs key.
So there are two ways to fix this:
If you change your action type to be more specific and include all the specific action types, your reducer should go back to type-checking.
Otherwise, add a dummy case, like | {type: "NOT-REAL"} so that Flow forces your reducer to have a default case for actions it doesn't understand.
I have this interface:
interface IFormData {
[string]: string
};
export type { IFormData };
It's simple interface that accepts key-value only string. But when I use this,
const formData:IFormData = { email: '...', password: '...' };
it gives me this error:
[flow] property $key of IFormData (Indexable signature not found in object literal)
I also tried this, but it gives me same error:
var formData: IFormData; // Error
formData['email'] = ...;
formData['password'] = ...;
I searched this on google almost 2 days, but still stuck in here and I need some help!
Any advice will very appreciate it.
If you switch from interface to type, Flow seems to be a lot happier with this sort of thing:
type IFormData = {
[string]: string
}
const formData: IFormData = { email: '...', password: '...' };
I'm guessing the fact that interfaces are nominally typed, rather than structurally typed, has something to do with this.