Getting Flow warning messages with Reducer - flowtype

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};

Related

how to fix issue with action type reducer

I've created react app with react-reducer.
I've declared types for actions and in the main Reduce I face an issue:
Can't read property type of undefined
import {ADD_USERS, DELETE_USER, GET_USERS} from '../types'
const initialState = {
users: [
{
id: 1,
name: 'Oksana'
},
{
id: 2,
name: 'Serge'
},
],
loading: true
}
export default function(state = initialState, action){
switch(action.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
case ADD_USERS:
const newId = state.users[state.users.length-1] + 1
return {
...state,
users: {
[newId] : {
id: newId,
name: action.name
}
},
loading: false
}
case DELETE_USER :
return {
...state,
users: state.users.filter(i => i.id !== action.id)
}
default: return state
}
}
so here I implement simple get/delete/add methods.
export function getUsers (name) {
return {
type: GET_USERS,
payload: name
}
}
....
and this is actions file
I export all the actions, idk where could I make a mistake
the rest of actions I've not mentioned.
Your switch statement uses action.type, however action is undefined in at least one of the actions you pass to the reducer. Either add a default value to action or add a guard before the switch-statement like so:
export default function(state = initialState, action){
if (!action) {
return state
}
switch(action.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
...
As you have a default condition already, adding a default value to action, e.g. action = {} might be the cleaner solution.
If you use Typescript you could also just do the following:
export default function(state = initialState, action){
switch(action?.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
...

Use one filfilled for several createAsyncThunk functions

I have two createAsyncThunk - signIn and signUp is it possible to use one fulfilled reducer both? The reason is that fulfilled reducer same for signIn and signUp. Example:
extraReducers: (builder) => {
builder.addCase(signInUser.fulfilled, (state, { payload }) => {
state.isPending = false;
state.email = payload.username;
state.username = payload.username;
state.first_name = payload.first_name;
state.last_name = payload.last_name;
state.title = payload.title;
state.organization = payload.organization;
state.isNew = payload.isNew;
state.isPremium = payload.isPremium;
state.id = payload.id;
state.error = '';
state.access_token = payload.access_token;
localStorage.setItem(REFRESH_TOKEN, payload.refresh_token);
});
}
Yes, have utils for this purpose: watch this
There is an obvious solution that I found after refactoring, you can pass state and payload to another function and it can set state as it would in yourAsyncThunk.fulfilled. Example:
builder.addCase(signInUser.fulfilled, (state, { payload }) => {
assignStateWithUser(state, payload);
});
builder.addCase(signUpUser.fulfilled, (state, { payload }) => {
assignStateWithUser(state, payload);
});
builder.addCase(loadUser.fulfilled, (state, { payload }) => {
assignStateWithUser(state, payload);
});
const assignStateWithUser = (state: initialStateUser, payload: User) => {
state.email = payload.username;
state.username = payload.username;
state.first_name = payload.first_name;
state.last_name = payload.last_name;
state.title = payload.title;
state.organization = payload.organization;
};
Types for state and payload:
type initialStateUser = RequestPending & { error: string } & User;
export interface User {
id: number;
username: string;
first_name: string;
last_name: string;
email: string;
title: string;
organization: string;
};

Flow disjoint union checks work for literal strings but not constants

I'm trying to properly type redux actions with Flow but it's not typechecking disjoint types correctly when using constants for the action types.
I've read in many places that this is the right way to do it but I can't get the DOUBLE constant to error out (so this is not really typing the switch statement properly).
Is there another way to do this?
// #flow
export const INCREMENT: 'INCREMENT' = 'INCREMENT';
export type IncrementAction = {|
type: typeof INCREMENT,
|};
export const DECREMENT: 'DECREMENT' = 'DECREMENT';
export type DecrementAction = {|
type: typeof DECREMENT,
|};
export const DOUBLE: 'DOUBLE' = 'DOUBLE';
export type DoubleAction = {|
type: typeof DOUBLE,
|};
// Notice I didn't include DoubleAction
export type Action = IncrementAction | DecrementAction;
type State = {|
counter: number,
|};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case INCREMENT:
return { counter: state.counter + 1 };
case DECREMENT:
return { counter: state.counter - 1 };
// This case should fail because it's in none of the actions in the disjoint union
case DOUBLE:
return { counter: state.counter * 2 };
// This case does fail because it's a literal instead of a constants
case 'TRIPLE':
return { counter: state.counter * 3 };
default:
return state;
}
};

Allow partial type

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 🙂

Action declaration doesn't work properly

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`
}
}

Resources