Created
December 4, 2024 19:18
-
-
Save ARAldhafeeri/1ad10710bee110b9a88013984272fbbd to your computer and use it in GitHub Desktop.
higher order redux toolkit function that generate redundant logic when the APIs are standardized.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; | |
import instance from "../instance"; | |
import { message } from "antd"; | |
import { createListenerMiddleware, isAnyOf } from "@reduxjs/toolkit"; | |
const createEntitySlice = ({ | |
entityName, | |
endpoints, | |
extraReducers = () => {}, | |
extraThunks , | |
extraInitialState, | |
extraActions, | |
middlewareEnhancements = (middleware, thunks) => middleware, | |
}) => { | |
const initialState = { | |
data: [], | |
loading: true, | |
error: null, | |
limit: 10, | |
page: 1, | |
total: 1, | |
pageSize: 5, | |
search: "", | |
filter: "ALL", | |
stats: [], | |
}; | |
// conditional extra state | |
Object.assign(initialState, extraInitialState); | |
// Base Thunks | |
const fetchEntity = createAsyncThunk(`${entityName}/fetch`, async (params, thunkAPI) => { | |
try { | |
const res = await instance.get(endpoints.FETCH(params.limit, params.page)); | |
return res.data; | |
} catch (error) { | |
console.error(`Error creating ${entityName}:`, error); // Log the error | |
return thunkAPI.rejectWithValue(error?.response?.data?.message); | |
} | |
}); | |
const createEntity = createAsyncThunk(`${entityName}/create`, async (data, thunkAPI) => { | |
try { | |
const res = await instance.post(endpoints.BASE, data); | |
return res.data; | |
} catch (error) { | |
console.error(`Error creating ${entityName}:`, error); // Log the error | |
return thunkAPI.rejectWithValue(error?.response?.data?.message); | |
} | |
}); | |
const updateEntity = createAsyncThunk(`${entityName}/update`, async (data, thunkAPI) => { | |
try { | |
const res = await instance.put(endpoints.WITH_ID(data.id), data); | |
return res.data; | |
} catch (error) { | |
console.error(`Error creating ${entityName}:`, error); // Log the error | |
return thunkAPI.rejectWithValue(error?.response?.data?.message); | |
} | |
}); | |
const deleteEntity = createAsyncThunk(`${entityName}/delete`, async (data, thunkAPI) => { | |
try { | |
const res = await instance.delete(endpoints.WITH_ID(data.id)); | |
return res.data; | |
} catch (error) { | |
console.error(`Error creating ${entityName}:`, error); // Log the error | |
return thunkAPI.rejectWithValue(error?.response?.data?.message); | |
} | |
}); | |
const searchEntity = createAsyncThunk(`${entityName}/search`, async (params, thunkAPI) => { | |
try { | |
const res = await instance.post(endpoints.SEARCH(params.limit, params.page), { payload: params.payload }); | |
return res.data; | |
} catch (error) { | |
console.error(`Error creating ${entityName}:`, error); // Log the error | |
return thunkAPI.rejectWithValue(error?.response?.data?.message); | |
} | |
}); | |
// Combine Base Thunks with Additional Thunks | |
const thunks = { | |
fetchEntity, | |
createEntity, | |
updateEntity, | |
deleteEntity, | |
searchEntity, | |
}; | |
// conditional extra thunks | |
Object.assign(thunks, extraThunks); | |
// Slice | |
const entitySlice = createSlice({ | |
name: entityName, | |
initialState, | |
reducers: Object.assign({ | |
searchUpdate: (state, action) => { | |
state.search = action.payload; | |
}, | |
searchReset: (state) => { | |
state.search = ""; | |
state.limit = 5; | |
state.page = 1; | |
} | |
}, extraActions), | |
extraReducers: (builder) => { | |
builder | |
.addCase(fetchEntity.pending, (state) => { | |
state.loading = true; | |
}) | |
.addCase(fetchEntity.fulfilled, (state, action) => { | |
state.loading = false; | |
state.data = action.payload.data.data; | |
state.total = action.payload.data.total; | |
state.page = action.payload.data.page; | |
state.pageSize = action.payload.data.pageSize; | |
}) | |
.addCase(fetchEntity.rejected, (state) => { | |
state.loading = false; | |
state.error = true; | |
}) | |
.addCase(createEntity.fulfilled, (state, action) => { | |
state.data.push(action.payload.data); | |
}) | |
.addCase(updateEntity.fulfilled, (state, action) => { | |
const index = state.data.findIndex((item) => item._id === action.payload.data._id); | |
if (index !== -1) state.data[index] = { ...action.payload.data }; | |
}) | |
.addCase(deleteEntity.fulfilled, (state, action) => { | |
state.data = state.data.filter((item) => item._id !== action.payload.data._id); | |
}) | |
.addCase(searchEntity.fulfilled, (state, action) => { | |
state.data = action.payload.data.data; | |
state.total = action.payload.data.total; | |
state.page = action.payload.data.page; | |
state.pageSize = action.payload.data.pageSize; | |
}); | |
// Extend Reducers | |
extraReducers(builder, thunks); | |
} | |
}); | |
// Middleware | |
const listenerMiddleware = createListenerMiddleware(); | |
listenerMiddleware.startListening({ | |
matcher: isAnyOf(thunks.createEntity.rejected, thunks.updateEntity.rejected, thunks.deleteEntity.rejected, thunks.searchEntity.rejected), | |
effect: async (action) => { | |
message.error(action.payload?.message); | |
} | |
}); | |
listenerMiddleware.startListening({ | |
matcher: isAnyOf(thunks.createEntity.fulfilled, thunks.updateEntity.fulfilled, thunks.deleteEntity.fulfilled), | |
effect: async (action) => { | |
message.success(action.payload?.message); | |
} | |
}); | |
listenerMiddleware.startListening({ | |
predicate: (_action, currentState, previousState) => { | |
const entityState = currentState[entityName]; | |
return entityState.search !== previousState[entityName].search && entityState.search !== ""; | |
}, | |
effect: async (_action, listenerApi) => { | |
listenerApi.cancelActiveListeners(); | |
await listenerApi.delay(500); | |
const state = listenerApi.getState()[entityName]; | |
listenerApi.dispatch(thunks.searchEntity({ limit: state.limit, page: state.page, payload: state.search })); | |
} | |
}); | |
listenerMiddleware.startListening({ | |
actionCreator: entitySlice.actions.searchReset, | |
effect: async (_action, listenerApi) => { | |
listenerApi.cancelActiveListeners(); | |
await listenerApi.delay(200); | |
listenerApi.dispatch(thunks.fetchEntity({ limit: 10, page: 1 })); | |
} | |
}); | |
// Apply Middleware Enhancements | |
const enhancedMiddleware = middlewareEnhancements ? middlewareEnhancements(listenerMiddleware, thunks) : listenerMiddleware; | |
return { | |
reducer: entitySlice.reducer, | |
actions: entitySlice.actions, | |
thunks, | |
middleware: enhancedMiddleware | |
}; | |
}; | |
export default createEntitySlice; | |
// example of using extra thunks, initstate | |
/** | |
* const customStatsThunk = createAsyncThunk("reservation/stats", async (_, thunkAPI) => { | |
* try { | |
* const res = await instance.get(ENDPOINTS.SUBSCRIBED.RESERVATION.STATS); | |
* return res.data; | |
* } catch (error) { | |
* return thunkAPI.rejectWithValue(error?.response?.data?.message); | |
* } | |
* }); | |
* | |
* // Reservation Slice | |
* const reservationSlice = createEntitySlice({ | |
* entityName: "reservation", | |
* endpoints: ENDPOINTS.SUBSCRIBED.RESERVATION, | |
* extraInitialState: { | |
* stats: {} | |
* }, | |
* extraThunks: { | |
* fetchStats: customStatsThunk | |
* }, | |
* extraReducers: (builder, thunks) => { | |
* builder.addCase(thunks.fetchStats.fulfilled, (state, action) => { | |
* state.stats = action.payload.data; | |
* }); | |
* }, | |
* middlewareEnhancements: (middleware, thunks) => { | |
* middleware.startListening({ | |
* actionCreator: thunks.fetchStats.fulfilled, | |
* effect: async (action) => { | |
* message.success("Stats loaded successfully!"); | |
* } | |
* }); | |
* return middleware; | |
* } | |
* }); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment