aaronofleonard
aaronofleonard

Reputation: 2576

Redux-Actions handleActions Nested Reducers

Maybe I'm missing something completely obvious but this has been tripping me up today.

Let's say we have a Redux store with a structure like so:

const state = {
  ...
  pages: {
    ...
    accountPage: {
      currentTab: 'dashboard',
      fetching: false,
      tableSettings: {
        sortDir: 'asc',
        sortField: 'name'
      }
    }
  }
}

So there is obviously a main reducer...

export default combineReducers({
  ...
  pages: pagesReducer
  ...
});

Then the reducer for pages has the reducer for each page...

export default combineReducers({
  ...
  accountPage: accountPageReducer
  ...
});

And now finally we get down to the meat of the problem, the reducer for this particular piece of state.

export default handleActions({
   [setCurrentTab]: (state, action) => { ... },
   [setIsFetching]: (state, action) => { ... }
});

That's all good right? Well, the key in the state given at the outset at tableSettings should actually be handled by it's own reducer. This pattern may exist many times in the state, so it is abstracted away to a reducer-creating function:

const defaultState = {
  sortDir: 'asc',
  sortField: null
};

export const createTableSettingReducer (actions, extra ={}) => {
  return handleActions({
    [actions.changeSortDir]: (state, action) => ({ ...state, sortDir: action.payload }),
    [actions.changeSortField]: (state, action) => ({ ...state, sortField: action.payload }),
    ...extra
  }, defaultState)
}

So, above the reducer for the sections of state (accountPageReducer), we created the reducer:

// pretend these actions were imported
const tableSettingsReducer = createTableSettingReducer({
  changeSortDir: setSortDir,
  changeSortField: setSortField
});

So the question is, where do I put tableSettingsReducer?

This of course, doesn't work:

export default handleActions({
   [setCurrentTab]: (state, action) => { ... },
   [setIsFetching]: (state, action) => { ... },
   tableSettings: tableSettingsReducer
});

It doesn't work because handleActions expects to use the action constants as keys, not the actual key in the state.

There is also nowhere to use combineReducers, since there is only one nested reducer of this slice of state. currentTab and fetching do not need their own reducer, so it's fruitless to use combineReducers.

I know that recently redux-actions started support nested reducers...but there isn't really any documentation available showing exactly how it's supposed to be done, or even describing the parameters needed to make it happen.

I could possibly use combineActions, and combine all of the actions in handleActions for every action that can be taken by a nested reducer. But that doesn't seem very clean...plus, what if the nested reducer has it's own nested reducers? That means every time those reducers can process a new action, that action needs to be added to combineActions in all its parents. Not the best.

Thoughts?

Upvotes: 0

Views: 1056

Answers (2)

Hilal Aissani
Hilal Aissani

Reputation: 735

let say you have the initialState = { data : []}
  let assume that the upcoming action has payload of an array 
export the reducer as the following :

 return  handleActions({
       ["Action Type 1" ]: (state, { payload }) => {
        return { ...state, data:  [...state.data, ...payload ]} ;
       },
       ["Action Type 1" ]: (state, { payload }) => {
         return { ...state, data:  [...state.data, ...payload ]} ;
       },
    }, initialSate );

    import this reducer in your combine reducer .

Upvotes: 0

abigezunt
abigezunt

Reputation: 66

Every key in your state gets its own reducer. Some reducers are really simple, some are themselves composed of other reducers. All the sister keys at each level of your state tree can be combined with combineReducers.

    const initialCurrentTab = 'dashboard';
    const currentTabReducer = handleActions({
       [setCurrentTab]: (state, action) => {
         return action.payload;
       },
    }, initialCurrentTab);

    const defaultFetchingState = false;
    const fetchingReducer = handleActions({
       [setIsFetching]: (state, action) => {
         return action.payload;
       },
    }, defaultFetchingState);

    export default combineReducers({
      currentTab: currentTabReducer,
      fetching: fetchingReducer,
      tableSettings: tableSettingsReducer,
    });

Upvotes: 2

Related Questions