2

Some preface, see this post for how I implemented a barcode scanner. Long story short, when a barcode was scanned, it fired an event in jQuery, which allowed me in my components to do the following:

class EmployeeScreen extends Component {
  componentDidMount() {
    $(window).on('barcodeScan', (e) => {
      if ( e.state.type !== 'E' ) {
        alert('Please scan your employee badge');
      } else {
        this.setState({
          badge: e.state.code
        });
      }
    });
  }
}

This was nice, because I was able to, on this specific component, check for employee badges and display errors otherwise. Now, I'm switching to a redux style, and I'm having trouble figuring out the best way to approach this. I figured the best way to start was to fire off a BARCODE_READ action with redux-thunk:

dispatch({
  type: BARCODE_READ,
  event: {
    type: this.barcodeRead.substr(0, 1),
    code: this.barcodeRead.substr(1),
  }
}));

The problem is, multiple reducers throughout my app need to listen for BARCODE_READ, and do different actions. For example, let's say I have an following screens:

  • Employee Screen: Using badge barcode, fetches employee information (First/Last name) and displays on page.
  • Supervisor Screen: Using badge barcode, fetches employees who work under the user, and displays on the page.

Because this action is a part of my global app actions, I'm unsure how to apply it to individual screen actions. One way to do this, would be to dispatch actions from my employee reducer, but I've read this is an anti-pattern:

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case BARCODE_READ:
            if (action.event.type === 'E') {
              dispatch({type: 'FETCH_EMPLOYEE_RECORD'});
            }
            return state;
        case FETCH_EMPLOYEE_SUCCESS:
            return {
                ...state,
                employee: action.employee,
            };
        default: {
            return state;
        }
    }
};

My supervisor reducer may dispatch FETCH_SUPERVSIOR_RECORD, and views should process/update accordingly. Because this is an anti-pattern (And the fact I don't have access to dispatch from the reducer), what is the recommended approach?

Blue
  • 22,608
  • 7
  • 62
  • 92
  • How about a middleware, it will intercept actions, and you should have access to dispatch function. ex: // const middleware = store => next => action => next(action); if(action.event.type==='E') dispatch({type: 'FETCH_EMPLOYEE_RECORD'}) – Mohamed El-Sayed Jan 16 '18 at 20:33
  • @MohamedEl-Sayed How will the middleware work with separate folders for each of the reducers/actions? – Blue Jan 16 '18 at 20:42
  • Middlewares are global, and will intercept every action, you just need to call applyMiddleware when creating the store. // myStore = createStore(rootReducer, applyMiddleware(theMiddleware)); or with compose // myStore = createStore(rootReducer, compose(enhancer, applyMiddleware(theMiddleware))) – Mohamed El-Sayed Jan 16 '18 at 20:46
  • Can you post your action creators? Since your title mentions `'redux-thunk'`, your action creator should return a function that dispatches multiple actions. – Ross Allen Jan 16 '18 at 21:39

2 Answers2

1

This case is tailor made for a side-effect handling middleware like Redux-Observable.

All your reducers should do is take an action and return a new copy of the state.

Any time you interact with an external service (EG REST API, database,) or perform a long-running piece of work, you want to do that outside of your reducers. You can dispatch a custom action from your component and have your 'epic' (Redux-Observable handler) listen for that action.

Another similar library is Redux-Saga, both do basically the same thing.

Matt Morgan
  • 4,900
  • 4
  • 21
  • 30
1

It is correct that the reducers should not fire new actions. Reducers should only mutate the state of the application with no other side effects.

The correct way to handle this is in your actionCreator. Since you are using redux thunk, I guess your action creator for barcode reading looks something like this:

function barcodeRead() {
  return dispatch => {
    dispatch({
      type: BARCODE_READ,
      event: {
        type: this.barcodeRead.substr(0, 1),
        code: this.barcodeRead.substr(1),
      }
    }));
  };
}

This is the place to do your business logic! Here is some pseudo code on how your new action creator might look:

function barcodeRead() {
  return dispatch => {
    dispatch({
      type: BARCODE_READ,
      event: {
        type: this.barcodeRead.substr(0, 1),
        code: this.barcodeRead.substr(1),
      }
    }));
   if(employeeScreenShowing) {
     dispatch(updateEmployeeScreen));
   }
  };
}
pgsandstrom
  • 14,361
  • 13
  • 70
  • 104
  • The problem with this approach, is now my global action creator now needs department specific logic. I'd like to ideally separate this logic into each departments code. – Blue Jan 17 '18 at 16:33
  • If you want complete separation, then you should to use something like redux-observable as suggested in the other answer. But that can be quite a bit of work. There are other possible solutions. Your other components could take in the barcode data as props and detect changes in the componentWillReceiveProps lifecycle method (see https://reactjs.org/docs/react-component.html#updating-componentwillreceiveprops). This is a good pattern if you only want to update when certain components are mounted. But otherwise I would still recommend the solution I gave. – pgsandstrom Jan 17 '18 at 20:40