I have the following React reducer which handles my state for the Profile Module. Although this code work fine, I think I'm doing it wrong because I'm handling the Loading states and Profile state in the same reducer.
function profileReducer(state, action) {
switch (action.type) {
case 'LOADING_START': {
return { ...state, status: 'pending' }
}
case 'LOADING_FAILED': {
return { ...state, status: 'rejected', error: action.payload }
}
case 'LOADING_SUCCESS': {
return { ...state, status: 'resolved' }
}
case 'PROFILES_GET': {
return { ...state, profiles: action.payload }
}
case 'PROFILE_EDIT': {
return { ...state, profile: action.payload }
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
function ProfileProvider({ children }) {
const [state, dispatch] = React.useReducer(profileReducer, {
status: null,
error: null,
profiles: [],
profile: null,
})
const value = [state, dispatch];
return <ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
}
The loading states are duplicated not only in the profile module. It is also used in other modules as well. Is this not code duplication? If so how can I handle this duplication.
PS
I'm using this actions in the following way
async function getProfiles(dispatch) {
dispatch({ type: 'LOADING_START' })
try {
const profiles = await client.getProfiles()
dispatch({ type: 'LOADING_SUCCESS' })
dispatch({ type: 'PROFILES_GET', payload: profiles })
} catch (error) {
dispatch({ type: 'LOADING_FAILED', payload: error })
throw error;
}
}
async function addProfile(dispatch, profile) {
dispatch({ type: 'LOADING_START' })
try {
const addedProfile = await client.addProfile(profile)
const profiles = await client.getProfiles()
dispatch({ type: 'PROFILES_GET', payload: profiles })
dispatch({ type: 'LOADING_SUCCESS' })
} catch (error) {
dispatch({ type: 'LOADING_FAILED', payload: error })
throw error;
}
}
CodePudding user response:
If you want to reuse these specific loading actions between your reducers, you could write a higher-order reducer which augments the more specified ones:
function withLoading(reducer) {
return (state, action) => {
switch(action.type) {
case 'LOADING_START': {
return { ...state, status: 'pending' }
}
case 'LOADING_FAILED': {
return { ...state, status: 'rejected', error: action.payload }
}
case 'LOADING_SUCCESS': {
return { ...state, status: 'resolved' }
}
default: return reducer(state, action);
}
}
}
In use:
function profileReducer(state, action) {
switch (action.type) {
case 'PROFILES_GET': {
return { ...state, profiles: action.payload }
}
case 'PROFILE_EDIT': {
return { ...state, profile: action.payload }
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
const combinedReducer = withLoading(profileReducer);
I think this is the only way that you can usefully reuse code here. Having a generic loading reducer which you create multiples of to access alongside the specific ones just seems like it would add more complexity.