5

I am wondering if there is anything wrong with creating generic actions in redux which dynamically change different properties in the store , so that i can reduce boilerplate code.

For example:

//GenericActions.js

function replace(data, property) {
  return { 
        actionType: GenericConstants.ADD
        data,
        property
    }
}
export default replace;

//GenericReducer.js

function genericReducer(state, action) {
    switch(action.actionType) {
        case GenericConstants.ADD:
            let change = {};
            let change[action.property] = [ ...state[action.property], action.data];
            return Object.assign({}, state, change);
    }
}
export default genericReducer;
Mr. MonoChrome
  • 195
  • 1
  • 4
  • If you're interested - I've been working on a kind of framework that does this: see my question here for the code as it currently is: https://softwareengineering.stackexchange.com/questions/390181/is-there-a-term-that-refers-to-the-bundle-of-actions-action-creators-reducers – dwjohnston Apr 11 '19 at 06:13

1 Answers1

1

I know this is a bit old, but I've recently switched to a similar pattern as my application has grown. As the UI and data requirements grow more complex, I've found that we've ended up with an unmaintanable list of action types, many of which are doing exactly the same thing on slightly different bits of data. In my new UI structure we have something like this (not these exact values, but the same style). First we define constants that make up the applications UI:

/* ui-constants.js */

export const FORM = 'FORM';
export const NAV = 'NAV';
export const DLG_ALERT = 'DLG_ALERT';
export const DLG_ERROR = 'DLG_ERROR';

export const UI_KEYS = {
    VISIBLE: 'visible',
    TITLE: 'title',
    MESSAGE: 'message'
    ENABLED: 'enabled'
};

Then our initial UI state consists of these constants.

/* state-ui.js */

const UIState = {
    [UI_KEYS.VISIBLE]: {
        [FORM]: false,
        [NAV]: false,
        [DLG_ALERT]: false,
        [DLG_ERROR]: false
    },
    [UI_KEYS.ENABLED]: {
        [FORM]: false,
        [NAV]: false,
        [DLG_ALERT]: false,
        [DLG_ERROR]: false
    },
    [UI_KEYS.MESSAGE]: {
        [FORM]: '',
        [DLG_ALERT]: '',
        [DLG_ERROR]: ''
    },
    [UI_KEYS.TITLE]: {
        [DLG_ALERT]: '',
        [DLG_ERROR]: ''
    }
};

This then allows us to use generic actions to switch parts of the UI based on the UI Keys. So for example we have actions like this

{
    type: UI_TOGGLE, // constant defined elsewhere
    ui: DLG_ALERT,
    key: UI_KEYS.ENABLED
}

Then the UI reducer handles it like this

switch(type) {
    case UI_TOGGLE:
        newState[action.key][action.ui] = !state[action.key][action.ui];
    ...
}

We do a similar thing for our application data since it's largely CRUDing(?) of said data, and it works in a similar manner.

The downside that I can see I guess, is that it sort of breaks the pattern of a repeated action having the exact same effect any time it's called, as UI_TOGGLE could toggle any number of things within the UI. However, I think this only applies if you consider the action itself to be defined by its type and only its type. We have found a single string constant to be very limited in it's ability to describe an action, so instead the actions 'signature' if you will, is considered to be a sum of all its parts.

I believe The type property in this style is more true to it's meaning, in the sense that we are simply defining the "method" or "description" of the action which is going to take place, rather than additionally containing the parts of the state that it will affect.

Take the SET_VISIBLITY_FILTER example from the Redux docs. The "type" of the action is not just describing what's going to happen (in this case, setting a filter value), it's also describing that it is the visibility filter that will be affected. If you also want to have an highlighted filter for example you will need two actions:

{
    type: SET_VISIBILITY_FILTER,
    filter: SHOW_COMPLETED
},
{
    type: SET_HIGHLIGHTED_FILTER,
    filter: SHOW_INCOMPLETE
}

With a generic action, this can be reduced to a single action with a single reducer function

{
    type: SET_FILTER,
    filter: VISIBLITY,
    value: SHOW_COMPLETED
}

This way of doing things does require you to think more about your state tree, and how all parts will be represented and modified. But I think that in the long run, this is actually a good thing, as it forces you to construct a robust state tree, rather than just dumping state into different variable names as they pop into your head.

So personally yes, I think this is an acceptable way to do things, and you are correct in saying it reduces boilerplate code. While it adds an extra amount of code to your actions, it removes a tonne of reducer code. Since the reducer is the only place where state are going to occur, I feel that it actually makes it easier to find problems.

James Hay
  • 111
  • 3