Software Engineering Musings

Reflecting on Redux

May 29, 2020

I’ve had recent involvement in projects where the engineering team didn’t have much Redux experience, if any at all. Suffice to say, I saw some fairly shocking code, and while it’s okay to be shocked, it’s not okay to forget that we were once there, that that was once us.

In fact, I’d go so far as to argue that even the most experienced engineers would not do very well on their first shot at using Redux. It’s pretty weird at first.

So, with the above in mind, I decided to revisit my early uses of Redux and just bare all.

The Early Days

I didn’t have any real help when I was learning Redux, or React for that matter, so I had to figure out a lot of it as I went. Often this meant a lot of learning the hard way, as well as refactoring.

Below is a sample, albeit with some parts omitted or simplified for brevity, of some very early Redux that I wrote;

// action types
const accountConstants = {
  LOGIN_REQUEST = 'LOGIN_REQUEST',
  LOGIN_SUCCESS = 'LOGIN_SUCCESS',
  LOGIN_FAILURE = 'LOGIN_FAILURE',
};

// action
function accountLogin(data) {
  return (dispatch) => {
    dispatch(request(data));
    accountService.login(data).then(
      (data) => {
        dispatch(success(data));
      },
      (failure) => {
        dispatch(failure(error));
      }
    );
  }
  function request(data) {
    return { type: accountConstants.LOGIN_REQUEST, data };
  }
  function success(data) {
    return { type: accountConstants.LOGIN_SUCCESS, data };
  }
  function failure(error) {
    return { type: accountConstants.LOGIN_FAILURE, error };
  }
}

// reducer
function accountReducer(state = {}, action) {
  switch (action.type) {
    case accountConstants.LOGIN_REQUEST:
      return { ...state };
    case accountConstants.LOGIN_SUCCESS:
      return { ...state };
    case accountConstants.LOGIN_FAILURE:
      return { ...state };
  }
}

The trained eye may, correctly, notice use of redux-thunk, which I didn’t entirely understand at the time, outside of knowing I needed it for async actions.

A Naive Approach

I don’t think the above is necessarily terrible, but it is naive. It was, however, the best I knew how do at the time and it did, at least, work.

Maybe it wasn’t even that bad a use at the time? I’ll never know.

The biggest problem with this approach, in my view at least, is that it scales very poorly. Looking at only the above that may be hard to imagine, and certainly in a small app it’s probably going to be just fine. However, when you have 15+ reducers with 400+ actions you’re in for an exhausting time.

Pain and exhaustion aside, I sure did get to know Redux intimately.

To the Future

While Redux is still Redux, the ecosystem around it and tooling for it have grown in ways that are difficult to imagine in this historical context. And that’s without mentioning the joy that TypeScript brings.

In particular there’s Redux Toolkit. I’ll admit that I didn’t love it immediately, mostly because it abstracts so much away, but that’s what I came to love about it the most.

By using createSlice you have actions and your reducer created in one hit, as shown below;

const slice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setTokens(state, action: PayloadAction<Tokens>) {
      const { payload } = action;
      state.accessToken = payload.accessToken;
    },
  },
});

I’m not sure I like mutable code in reducers*. I’m sure it’ll make the lives of many developers easier, but I feel it detracts from the functional nature of Redux and I think there’s great value in how unforgiving it is when you get that wrong. That’s also what I feel about, and one of the reasons why I like, C.

But with that said, it does bring an opinionated way to implement Redux and a pattern that will no doubt be widely adopted, and I’m not sure any criticisms I have stand up to this whatsoever.

* Yes I know it’s still using FP internally.

Overall

Redux used to be weird and require learning a lot of new concepts, I think that’s evident in how I used to write it. But if you’re learning or using Redux today, I’d suggest making use of Redux Toolkit, I most certainly am.

However I think it’s a shame that some very basic, foundational concepts of functional programming are ‘lost to time’ in a sense, especially as this is how a great many devs would gain their first exposure to said concepts - I’m looking at you, immutability.

Finally, I don’t know that I’m ready to embrace createAsyncThunk with open arms. It’s fine for simple uses, but I don’t think it’s necessarily capable of meeting the demands of a complex application. That involes function composition which is a more difficult to grasp functional programming concept, but that’s a conversation for another time.