Reputation: 683
I have a redux slice entity which I am using to store a state of an array. The array itself contains nested properties of array of objects. Now I need to update that state in redux saga and I am have tried to create another generator function but unable to figure out in it how can I update the state. I only need to update the state and API call will be made later. How can I update the state?
Here is the interface of task and deliveryParcels
export interface IRiderTask {
_id?: any;
riderId: string;
totalAmount: number;
riderName: string;
cityId: string;
cityName: string;
status: string;
adminName: string;
adminId: string;
createdAt: Date;
deliveryParcels: IDeliveryParcels[];
}
export interface IDeliveryParcels {
parcelId: string;
processingStatus?: string;
amount: number;
orderType: 'COD' | 'NONCOD';
postedStatus?: {
status: string;
statusKey: string;
signature?: string;
checkboxData?: any[];
reason: string;
adminId?: string;
adminName?: string;
};
}
I will be updating the postedStatus object each time with different values so need to handle it in generator function of saga.
Here is my saga
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
EntityState,
PayloadAction,
} from '@reduxjs/toolkit';
import { IRiderTask } from '@swyft/interfaces';
import { put, takeEvery } from 'redux-saga/effects';
import { select } from 'typed-redux-saga/dist';
import { DeliveryManagementState } from '../state';
export const finalStatus_FEATURE_KEY = 'finalStatus';
type Processing = 'initial' | 'processing' | 'processed' | 'processing error';
/*
* Update these interfaces according to your requirements.
*/
export interface finalStatusEntity extends IRiderTask {
_id?: any;
}
export interface finalStatusState extends EntityState<finalStatusEntity> {
loadingStatus: Processing;
updatingRequest: Processing;
error: string | null;
}
export const finalStatusAdapter = createEntityAdapter<finalStatusEntity>({
selectId: (e) => e._id,
});
export const initialfinalStatusState: finalStatusState = finalStatusAdapter.getInitialState(
{
loadingStatus: 'initial',
updatingRequest: 'initial',
error: null,
}
);
export const finalStatusSlice = createSlice({
name: finalStatus_FEATURE_KEY,
initialState: initialfinalStatusState,
reducers: {
add: finalStatusAdapter.addOne,
remove: finalStatusAdapter.removeOne,
setLoading: (state, action: PayloadAction<Processing>) => {
state.loadingStatus = action.payload;
},
setTasksState: (state, action: PayloadAction<{ data: any }>) => {},
setUpdatedState: (state, action: PayloadAction<Processing>) => {
state.updatingRequest = action.payload;
},
},
});
/*
* Export reducer for store configuration.
*/
export const finalStatusReducer = finalStatusSlice.reducer;
export const finalStatusActions = finalStatusSlice.actions;
const { selectAll, selectEntities } = finalStatusAdapter.getSelectors();
export const getfinalStatusState = (
rootState: DeliveryManagementState
): finalStatusState => rootState.deliveryPilet.finalStatus;
export const selectAllfinalStatus = createSelector(
getfinalStatusState,
selectAll
);
export const selectfinalStatusEntities = createSelector(
getfinalStatusState,
selectEntities
);
export function* finalStatusRootSaga() {
yield takeEvery('finalStatus/setTasksState', setTasksState);
yield takeEvery('finalStatus/updateTaskState', updateTaskState);
}
function* setTasksState({ payload: { data } }: { payload: { data: any } }) {
console.log('In slice', data);
yield put(finalStatusActions.add(data));
console.log(data);
}
function* updateTaskState(action: PayloadAction<{ id: string; data: any }>) {
yield put(finalStatusActions.setUpdatedState('processing'));
yield put(
finalStatusActions.setUpdatedState({
id: action.payload.id,
...action.payload.data,
})
);
yield put(finalStatusActions.setUpdatedState('processed'));
}
Upvotes: 1
Views: 1290
Reputation: 42170
There are two separate issues here. One is creating actions and reducer cases to properly update the data. The other is dispatching some of those actions through the saga. Quite frankly I don't really understand what your saga is supposed to be doing, and I'm not sure if you do either. But I can definitely make some improvements.
Do the "processing" states apply to the entire slice, or to each task?
Rather than updating parcels by updating elements of the array I think that it's better to treat them as a separate entity.
saga
import { EntityId, PayloadAction, Update } from "@reduxjs/toolkit";
import { finalStatusActions, FinalStatusEntity } from "./slice";
import { put, all, takeEvery, call } from "redux-saga/effects";
// dummy for API call
const postUpdates = async (
id: EntityId,
changes: Partial<FinalStatusEntity>
): Promise<Partial<FinalStatusEntity>> => {
return changes;
};
function* updateTaskState(action: PayloadAction<Update<FinalStatusEntity>>) {
const { id, changes } = action.payload;
try {
const result = yield call(postUpdates, id, changes);
yield put(
finalStatusActions.updateTaskStateSuccess({
id,
changes: { ...result, status: "processed" }
})
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
yield put(
finalStatusActions.updateTaskStateFailure({ id, error: message })
);
}
}
export function* finalStatusRootSaga() {
yield all([
takeEvery(finalStatusActions.updateTaskState.type, updateTaskState)
]);
}
export default finalStatusRootSaga;
store
import {
AnyAction,
combineReducers,
configureStore,
getDefaultMiddleware
} from "@reduxjs/toolkit";
import {
createSelectorHook,
useDispatch as useBasicDispatch
} from "react-redux";
import finalStatus, { parcelSlice } from "./slice";
import createSagaMiddleware from "redux-saga";
import mySaga from "./saga";
const sagaMiddleware = createSagaMiddleware();
const deliveryPilet = combineReducers({
finalStatus,
parcels: parcelSlice.reducer
});
const store = configureStore({
reducer: {
deliveryPilet
},
middleware: [...getDefaultMiddleware(), sagaMiddleware]
});
sagaMiddleware.run(mySaga);
export type RootState = ReturnType<typeof store.getState>;
export type DeliveryManagementState = RootState;
export type AppDispatch = typeof store.dispatch;
export type Action = AnyAction;
export const useDispatch = (): AppDispatch => useBasicDispatch();
export const useSelector = createSelectorHook<RootState>();
export default store;
tasks
// helper for adding a task
const taskToEntity = (task: IRiderTask): FinalStatusEntity => ({
...task,
parcelIds: task.deliveryParcels.map((o) => o.parcelId)
});
export const finalStatusAdapter = createEntityAdapter<FinalStatusEntity>({
selectId: (entity) => entity._id
});
export const initialfinalStatusState = finalStatusAdapter.getInitialState();
export const finalStatusSlice = createSlice({
name: finalStatus_FEATURE_KEY,
initialState: initialfinalStatusState,
reducers: {
// accept in the form of an IRiderTask instead of FinalStatusEntity
addTask: (state, action: PayloadAction<IRiderTask>) => {
finalStatusAdapter.addOne(state, taskToEntity(action.payload));
},
removeTask: finalStatusAdapter.removeOne,
updateTaskState: (
state,
action: PayloadAction<Update<FinalStatusEntity>>
) => {
// here we just start the update, the saga will dispatch the rest
const { id } = action.payload;
finalStatusAdapter.updateOne(state, {
id,
changes: { status: "processing" }
});
},
updateTaskStateSuccess: finalStatusAdapter.updateOne,
updateTaskStateFailure: (
state,
action: PayloadAction<{ id: EntityId; error: string }>
) => {
const { id, error } = action.payload;
finalStatusAdapter.updateOne(state, {
id,
changes: { status: "processing error", error }
});
}
}
});
export const finalStatusActions = finalStatusSlice.actions;
export default finalStatusReducer;
parcels
export const parcelAdapter = createEntityAdapter<IDeliveryParcels>({
selectId: (parcel) => parcel.parcelId
});
export const parcelSlice = createSlice({
name: "parcels",
initialState: parcelAdapter.getInitialState(),
reducers: {
updateParcel: parcelAdapter.updateOne,
// handle nested update
updatePostedStatus: (
state,
action: PayloadAction<Update<IPostedStatus>>
) => {
const { id, changes } = action.payload;
const current = state.entities[id];
if (current) {
current.postedStatus = {
...current.postedStatus,
...changes
} as IPostedStatus; // I don't know why this is necessary, but I'm getting TS errors
}
}
},
extraReducers: {
// add the parcels when adding a task
[finalStatusActions.addTask.type]: (
state,
action: PayloadAction<IRiderTask>
) => {
parcelAdapter.upsertMany(state, action.payload.deliveryParcels);
}
}
});
types
import { EntityId } from "@reduxjs/toolkit";
export interface IRiderTask {
_id: EntityId;
riderId: string;
totalAmount: number;
riderName: string;
cityId: string;
cityName: string;
status: string;
adminName: string;
adminId: string;
createdAt: Date;
deliveryParcels: IDeliveryParcels[];
}
export interface IPostedStatus {
status: string;
statusKey: string;
signature?: string;
checkboxData?: any[];
reason: string;
adminId?: string;
adminName?: string;
}
export interface IDeliveryParcels {
parcelId: string;
processingStatus?: string;
amount: number;
orderType: "COD" | "NONCOD";
postedStatus?: IPostedStatus;
}
export type Processing =
| "initial"
| "processing"
| "processed"
| "processing error";
// replace parcel objects with ids, include error
export type FinalStatusEntity = Omit<IRiderTask, "deliveryParcels"> & {
parcelIds: string[];
error?: string;
};
Upvotes: 2