最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to cancelignore an action in redux - Stack Overflow

programmeradmin2浏览0评论

Is there a way to cancel an action or ignore it?

Or rather what is the best/recommended way to ignore an action?

I have the following action creator and when I input an invalid size (say 'some_string') into the action creator, in addition to getting my own warning message I also get: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.

import { SET_SELECTED_PHOTOS_SIZE } from './_reducers';

export default (size=0) => {
  if (!isNaN(parseFloat(size))) {
    return {
      type: SET_SELECTED_PHOTOS_SIZE,
      size: size,
    };
  } else {
    app.warn('Size is not defined or not a number');
  }
};

I've discussed this in the redux-channel in Discord (reactiflux) where one suggestion was to use redux-thunk like this:

export default size => dispatch => {
  if (!isNaN(parseFloat(size))) {
    dispatch({
      type: SET_SELECTED_PHOTOS_SIZE,
      size: size,
    });
  } else {
    app.warn('Size is not defined or not a number');
  }
}

The other option was to ignore the action inside the reducer. This does make the reducer "fatter" because it then has more responsibilities, but it uses less thunk-actions which makes it easier to debug. I could see the thunk-pattern getting out of hand since I would be forced to use it for almost every action, making batched actions a bit of a pain to maintain if you have lots of them.

Is there a way to cancel an action or ignore it?

Or rather what is the best/recommended way to ignore an action?

I have the following action creator and when I input an invalid size (say 'some_string') into the action creator, in addition to getting my own warning message I also get: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.

import { SET_SELECTED_PHOTOS_SIZE } from './_reducers';

export default (size=0) => {
  if (!isNaN(parseFloat(size))) {
    return {
      type: SET_SELECTED_PHOTOS_SIZE,
      size: size,
    };
  } else {
    app.warn('Size is not defined or not a number');
  }
};

I've discussed this in the redux-channel in Discord (reactiflux) where one suggestion was to use redux-thunk like this:

export default size => dispatch => {
  if (!isNaN(parseFloat(size))) {
    dispatch({
      type: SET_SELECTED_PHOTOS_SIZE,
      size: size,
    });
  } else {
    app.warn('Size is not defined or not a number');
  }
}

The other option was to ignore the action inside the reducer. This does make the reducer "fatter" because it then has more responsibilities, but it uses less thunk-actions which makes it easier to debug. I could see the thunk-pattern getting out of hand since I would be forced to use it for almost every action, making batched actions a bit of a pain to maintain if you have lots of them.

Share Improve this question asked Dec 9, 2015 at 17:03 StoikertyStoikerty 6711 gold badge7 silver badges16 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 14

Ignoring actions in Action Creators is basically a way of treating them as Command Handlers, not Event Creators. When the User clicks the button it’s some kind of Event though.

So there are basically two ways how to solve the issue:

  1. The condition is inside action creator and thunk-middleware is used

    const cancelEdit = () => (dispatch, getState) => {
      if (!getState().isSaving) {
        dispatch({type: CANCEL_EDIT});
      }
    }
    
  2. The condition is inside reducer and no middleware is required

    function reducer(appState, action) {
      switch(action.type) {
       case: CANCEL_EDIT:
         if (!appState.isSaving) {
           return {...appState, editingRecord: null }
         } else {
           return appState;
         }
       default:
         return appState;
    
      }
    }
    

I strongly prefer treating UI interaction as Events instead of Commands and there two advantages:

  1. All your domain logic stays in the synchronous pure reducers which are very easy to test. Just imagine you would need to write unit test for the functionality.

    const state = {
      isSaving: true,
      editingRecord: 'FOO'
    };
    
    // State is not changed because Saving is in progress
    assert.deepEqual(
      reducer(state, {type: 'CANCEL_EDIT'}),
      state
    );
    
    // State has been changed because Saving is not in progress anymore
    assert.deepEqual(
      reducer({...state, isSaving: false}),
      {isSaving: false, editingRecord: null}
    );
    

As you can see the test is really simply when you treat the interaction as an Event

  1. What if you decided that instead of ignoring the action you would rather show some visual indication that the action is not possible? You would need to dispatch another action or basically rebuild it. However, you can’t use hot-reload with replay here because the logic in action creator is not re-playable. If the logic is in reducer though, you can simply change the behaviour, the reducer will get hot-reloaded and all the events gets replayed. The only event that you dispatch is that user clicked some button and you can’t deny that fact. So unless you drastically change the UI you can always hot-reload with replay.

When you think about any interaction with the UI as an Event then you will get the best possible replay experience, because Events can’t be denied they have just happened.

发布评论

评论列表(0)

  1. 暂无评论