Home > Mobile >  How to refactor this reducer following separation of concerns and avoiding code duplications?
How to refactor this reducer following separation of concerns and avoiding code duplications?

Time:10-07

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.

  • Related