Two mistakes with Redux code

Last week, I made two mistakes with my Redux code.

Mistake #1: deepClone Redux state

The state within the reducer is immutable. That is, the old state should never be changed. To ensure that the old state never gets changed, I got a “cool idea”. Do a deepClone of the entire state. So, my reducer code looked like:

case ActionType.SomeAction: {
  const newState = deepCopy(state);
  newState.someArray.push('abc');
  return newState;
}

The problem with deepCopy is that it clones every object in the old state. React compares the objects within the old state and the new state. If the objects differ, then that part of the component is re-rendered. By doing a deepCopy, everything gets re-rendered for every action. It causes performance problems. The right code is:

case ActionType.SomeAction: {
  const newState = Object.assign({}, state);
  newState.someArray = state.someArray.slice();
  newState.someArray.push('abc');
  return newState;
}

deepClone is a lazy way to avoid state mutations.

Mistake #2: Use Promise API for getting errors

When an action is dispatched via Redux prop, we don’t get any feedback about the errors. In a page with multiple buttons, it is very difficult to find out which error message corresponds to which button by looking at the error prop. So, I used the Promise API for getting some feedback about the AJAX call.

function ajaxCall() {
  return dispatch => {
    dispatch(ajaxCallPending());
    return new Promise((resolve, reject) => {
      fetch(api)
      .then(res => res.json())
      .then(json => {
        if (json.success) {
          dispatch(ajaxCallFulfilled(json.data));
          resolve();
        } else {
          dispatch(ajaxCallRejected());
          reject();
        }
      })
      .catch(err => {
        dispatch(ajaxCallRejected());
        reject();
      });
    });
  };
}

The event handler code is:

handleClick() {
  this.props.ajaxCall()
  .then(() => {
    this.setState({
      loading: false,
      success: 'Success message'
    });
  })
  .catch(() => {
    this.setState({
     loading: false,
     error: 'Error message'
   });
  });
}

For getting feedback about the AJAX call while using Redux, returning the fetch will give a Promise. There is no need to give success and error messages. Doing so will negate the value of Redux. Redux exists to manage uni-directional data flows. The success and error message should ideally be delivered via props from the reducer.

The code for dispatching the action is:

handleClick() {
  this.setState({ loading: true });
  this.props.ajaxCall()
  .then(() => {
    this.setState({
      loading: false
    });
  });
}

The code for displaying error message is a bit more subtle. Maybe, there are errors that need to be displayed for some actions and not for some other actions. Along with the error message, Redux should send an additional prop: the last action.

<div className="text-danger">
  {this.props.someReducer.error && 
   (this.props.someReducer.action === 'SOME_ACTION')}
</div>

Words of Caution

React is meant for JavaScript developers who like to write code. Taking advantage of development short-cuts like deepClone and Promise API breaks the very foundations of React and Redux.

The code written in reducer is very specific to the application. It mutates only a small part of the state which needs to be mutated. Writing such specific code might bloat up the code base. But, that is the very thing that makes React applications render fast.

Promise API may look like a convenient way to return user feedback from an action. Again, it breaks the uni-directional data flow paradigm for which Redux is designed for.

While working with React and Redux, understand some of these fundamental ideas, so that you don’t invent something like what I did last week.

Related Posts

One thought on “Two mistakes with Redux code

Leave a Reply to prashanth Cancel reply

Your email address will not be published.