using reducer namespace in redux to practice DRY

I want to add a loader for each action, like the buttons will display loading when dispatch (see demo)

export default class Items extends Component {
  render() {
    return (
      <div>
        <div>status: {this.props.item.status}</div>
        <button onClick={() => this.props.resetItem()}>
          {this.props.loading ? "loading..." : "Reset"}
        </button>
        <button onClick={() => this.props.approveItem()}>
          {this.props.loading ? "loading..." : "Approve"}
        </button>
      </div>
    );
  }
}

The problem is all button will show loading because my reducer has a global loading state only

export function items(state = initState, action) {
  switch (action.type) {
    case "APPROVE":
      return {
        ...state,
        loading: true
      };
    case "APPROVED":
      return {
        ...state,
        loading: false,
        item: {
          status: "approved"
        }
      };
    case "RESET":
      return {
        ...state,
        loading: true
      };
    case "DONE_RESET":
      return {
        ...state,
        loading: false,
        item: {
          status: "pending"
        }
      };
    default:
      return state;
  }
}

I can hardcode approve_loading, reset_loading and so on but that's redundancy, any technique to do namespacing in reducer?

1 answer

  • answered 2018-02-13 01:21 Tony

    Neat question - have never run into this myself but I'm wondering if something like this could work. You can use combineReducers() to namespace, so a perhaps not entirely elegant approach could be:

    export function itemsReducer(index) {
      return function items(state = {}, action) {
        switch (action.type) {
          case `APPROVE_${index}`:
            return {
              ...state,
              loading: true,
            };
          case `APPROVED_${index}`:
            return {
              ...state,
              loading: false,
              item: {
                status: 'approved',
              },
            };
          default:
            return state;
        }
      };
    }
    
    const reducers = {};
    //could add more indexes here for more items;
    [0, 1].forEach(i => {
      reducers[`item${i}`] = itemsReducer(i);
    });
    
    export default combineReducers(reducers);
    
    //state = {
    //  item0: {...},
    //  item1: {...}
    //}
    

    Your actions would then need to include the appropriate index (0 or 1) when dispatching (e.g. APPROVED_1) so the correct item state will get set.

    Equivalent syntax:

    export default combineReducers({
        item0: itemsReducer(0),
        item1: itemsReducer(1)
    });