Action declaration doesn't work properly - redux

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

Related

ReduxJS Toolkit action creator

I am currently converting the state management from old redux to reduxjs/toolkit.
This is how it looked previously:
ACTION:
export const RECEIVED_FUNCTION_SUCCESS = 'RECEIVED_FUNCTION_SUCCESS ';
export const getFunctionSuccess = (result, callbackParams) => {
const append = callbackParams.pageNumber > 0;
return {
type: RECEIVED_FUNCTION_SUCCESS,
payload: result,
append,
pageSize: callbackParams.pageSize,
};
};
REDUCER:
{
case ActionTypes.RECEIVED_FUNCTION_SUCCESS:
return {
...state,
canLoadMore: payload.length >= pageSize,
data: append ? [...state.data, ...payload] : payload,
};
This is how it looks now:
ACTION:
export const getFunctionSuccess = createAction('RECEIVED_FUNCTION_SUCCESS ');
REDUCER:
extraReducers: {
[getFunctionSuccess ]: (state, { payload }) => {
const { pageSize, pageNumber } = payload;
const append = pageNumber > 0;
return {
...state,
canLoadMore: payload.length >= pageSize,
data: append ? [...state.data, ...payload] : payload,
};
},
I can't make it work, pageSize and pageNumber is always undefined... I don't know how to include the callbackParams in the toolkit reducer and action.
For more context this is the main part of the fetching:
request({
requestId: 'getFunction',
params: {
data: {
filters: mappedFilters,
sorts: sort,
pageSize,
pageNumber,
},
},
callbackParams: {
pageNumber,
pageSize,
},
}),
I do fetch successfully and all, just the callbackParams, what is additional to the action after payload, I can't manage to make it work.
I'd appreciate some help.

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

Cannot assign to read only property 'state' of object '#<Object>'

I'm using Redux Toolkit and I'm having trouble in one of my actions. Here's relevant parts of my slice:
export const initialCookingSessionState = {
recipeInfo: null as RecipeInfo | null,
instructions: [] as Instruction[],
ingredients: [] as Ingredient[],
activeTimers: [] as CookingTimer[],
currentStepIndex: 0 as number,
stepTimers: [] as StepTimer[]
};
const cookingSessionSlice = createSlice({
name: 'session',
initialState: initialCookingSessionState,
reducers: {
startRecipe(state, { payload: recipe }: PayloadAction<Recipe>) {
const { info, instructions, ingredients } = recipe;
state.recipeInfo = info;
state.ingredients = [...ingredients];
state.instructions = [...instructions]
state.stepTimers = [];
state.instructions.forEach(({ timers }, stepIndex) => {
timers.forEach(timer =>
state.stepTimers.push({ ...timer, stepIndex, state: CookingTimerState.Pending })
)
})
},
incStep(state) { state.currentStepIndex++ },
decStep(state) { state.currentStepIndex-- },
startTimer(state, { payload: timer }: PayloadAction<StepTimer>) {
timer.state = CookingTimerState.Running
},
}
});
When I dispatch startTimer, I get the error:
Cannot assign to read only property 'state' of object '#'
There must be something about what is and isn't possible with Redux Toolkit's "Mutative State Changes" that I'm missing. It seems to me that my example isn't that different from theirs in the docs, but apparently I'm wrong about that. (the other actions work fine)
In case it's helpful, here are the models, which I think are pretty simple:
export class Recipe {
info: RecipeInfo = {
id: "",
title: ""
};
instructions: Instruction[] = [];
ingredients: Ingredient[] = []
}
export class Instruction {
timers: CookingTimer[] = [];
constructor(public text: string) {}
}
export class Ingredient {
id: string = "";
state: IngredientState = { done: false };
constructor(public text: string) {}
}
export class CookingTimer {
constructor(
public durationSec = 0,
public label = "") {}
}
export enum CookingTimerState {
Pending, Paused, Running, Done
}
export type StepTimer = {
state: CookingTimerState
durationSec: number
label: string
stepIndex: number
}

Getting Flow warning messages with Reducer

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

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 🙂

Resources