Seven Months Into Redux: Two Things My Team Learned Along The Way

D. Benjamin Ipsen
6 min readNov 11, 2016

--

Global pub sub patterns aren’t new in software development but Redux has exploded onto the front end scene. This isn’t a surprise given its elegant implementation, convenient React bindings and of course because everyone else is doing it.

For the past seven months I’ve been working on a project using React/Redux. I work with a lot of really smart folks and still we’ve been often left wondering “What’s the right way to _____ ?”

As a result (and because we are on schedule) we’ve now refactored our Redux modules three times.

This is a story of that progression as much as it can be a guide, hopefully it helps someone else get closer to the RightWay™ faster.

I’m assuming you’ve built a few things with Redux already or at least have your head around it somewhat.

DISCLAIMER: Code examples are brief, probably have errors and the ideas discussed can fairly be called opinions.

One: Converting Actions To Streams

Our UI is broadcasting events from action creator functions to our store, reducers are subscribed and eager to interpret those actions and pass it to the view. Neat.

We know that when you’re fetching data or doing some I/O with side effects that you’re supposed to handle this with a “thunk” or “saga” or “epic” or “logic” or “effects” or (insert any word that means the same thing here). No problem.

We have a working application where our actions are returning data but even our smaller scale app feels thick. We are using action creators to generate massive payloads computed from shared utilities. Some synchronous and others not.

These two ideas changed how we thought about actions:

  1. Actions with side effects (in a traditional sense) regardless of your choice of method are essentially a stream of events. “Actions in, actions out.” Yes! This makes RxJS a very lovely way to handle actions.
  2. More importantly some (but not all) actions without easily perceived “effects” can (and should) also be thought of as streams.

Let’s elaborate on number two. Say you have a UI component that responds to several different UI events, not uncommon. So a reducer that updates its state might look like this:

...
case SELECT_COLOR:
case SELECT_SIZE:
case SELECT_MATERIAL:
return {
...state,
...{ inStockMessage: action.payload.message }
};
...

There’s nothing wrong with this. The UI needs to respond to the state change and we’ve delivered the required information in a payload from our action:

const selectColor = createAction(SELECT_COLOR, (color) => {
const message = checkStockMessagesForColor(color);
return { color, message };
};

In this example, we assume checkStockMessagesForColor is a simple synchronous utility function. Let’s improve our reducer’s readability (or in more contemporary dialect its “reasonability”):

case IN_STOCK_MESSAGE_CHANGED:
return {
...state,
...{ inStockMessage: action.payload.message }
};

So we’ve changed our reducer to update state based on a single event rather than stacking them up. Did you also notice how we changed the tense of the action as well? Nice. Now let’s see how we can generate a simple “downstream” event from the original SELECT_COLOR action. This example is using redux-observable:

const selectColor = $action => // a stream of all actions
$action
.ofType(SELECT_COLOR) // "filter only this type"
.flatMap((action) => { // do the things
const { color } = action.payload;
const message = checkStockMessagesForColor(color);
return Observable.of(inStockChanged(message));
})
.switchMap((action) => {
// do even more things
return Observable.of(anotherDownstreamEvent());
})
.catch(error => Observable.of(handleError(error));

That may look a bit gnarly to anyone without any RxJS exposure but you can obtain the same improvement with a simple thunk:

const selectColor = color => (dispatch) => {
const message = checkStockMessagesForColor(color);
dispatch(inStockChanged(message));
dispatch(anotherDownstreamEvent());
}

I’d encourage anyone to learn more about Observable / RxJS since it’s awesome (wish I had more time with it) but the real take away is this:

By streaming your actions, you can make reducers and action flow easier to reason about by responding to downstream actions generated by UI actions… even if they are simple/synchronous!

Two: Move Logic & Derived Data to Selectors

A selector’s primary usage is as a function which takes state as its argument and returns an object which a React container component consumes as props. Perhaps you’re already using reselect for this? Way to be.

The result of moving logic into these functions has several benefits.

First, it removes logic from components (a.k.a the view):
Generally everyone can agree a view layer should be “dumb” or more descriptively, devoid of logic. We’ve taken the time to ensure that connected containers use a bunch of stateless functional components ready to accept their props as-is. Solid.

Unfortunately, we end up with quite a bit of code in our container components that looks something like this:

...
handleUserClick = (event) => {
if (this.props.userIsAdmin && this.props.userListIsVisible) {
dispatchUserClick();
}
}
...
render() {
const { users, searchTerm } = this.props;
const filteredUsers = users
.filter(user =>
user.active && user.name.indexOf(searchTerm) > -1);
<SimpleUserList
users={filteredUsers}
onClick={this.handleUserClick}
/>
...
}

So what’s the big deal? It’s just a container. We’re OK, right? Right?

Well… if it has a render method, probably fair to consider it a view. Not to mention, all that logic makes these a YUUGE (too soon?) pain in the a** to test!

So let’s get rid of that logic:

...
handleUserClick = (event) => {
dispatchUserClick();
}
...
render() {
<SimpleUserList
users={filteredUsers}
onClick={this.handleUserClick}
/>
...
}

Much better. So where did the logic go? First let’s encapsulate it into selectors with reselect’s createSelector function. We’ll start with the filtering logic:

export const filteredUserList = createSelector(
(state) => state.userList,
(state) => state.ui,
(userList, ui) =>
userList.filter(user =>
user.active && user.name.indexOf(ui.searchTerm) > -1);
);

We can then use this selector in the container’s connect function:

import { filteredUserList } from '../selectors/userList';
...
const selector = createSelector(
filteredUserList,
(users) => { filteredUsers: users }
);
export default connect(selector, dispatcher)(UserList)

So not only did we clean up our view, but reselect will memoize results of the function which can speed the performance if you do any heavy lifting. Sweet ‘lil bonus.

Maybe you knew that already, but either way let’s move on to that nasty if statement in our click handler. We can encapsulate that logic to a function that returns the boolean value, also using reselect:

export const userIsAdminAndListVisible = createSelector(
(state) => state.user,
(state) => state.ui,
(user, ui) => user.isAdmin && ui.userListVisible
);

Awesome, we have our boolean value derived from state that tells us when something should happen. So, where does this go if not in the component handler? You got it! Our new and improved action stream:

import { userIsAdminAndListVisible } from '../selectors/userList';
...
export const userDetailRequest = ($action, store) =>
$action
.filter(action =>
action.type === CLICK_USER || action.type === CLICK_USER_ICON)
)
.flatMap((action) => {
if (userIsAdminAndListVisible(store.getState())) {
// OK to do things!
}
});

We added some logic to control the flow of our actions stream and removed the logic from our view. Very cool.

A few additional benefits of moving logic to selectors:

Compossible, reusable and easy-to-test.
As a quick example, our userIsAdminAndListVisible bit of logic is could easily get dropped into a selector setting a prop for a component to hide or show something else. Good stuff.

If it seems overkill to encapsulate every bit of logic into an individual selector, you’re absolutely right. How you create and compose those functions will definitely change as you build out and refactor. This brings us to our parting thought:

Cleaner state and simple reducers:
If you’re thinking that you could perform derived computations and assign values to state in your reducers instead of selectors, you’re right again. Isn’t that where that logic should go? The answer is… it depends.

Your reducers will still serve their purpose. After all, the reducers are why it’s called Redux. That said, moving derived computation into selectors has clear benefits and you will end up with less logic in reducers, fewer switch cases and smaller payloads (thanks to the action streams).

Making tactful distinctions between true state (reducer logic) and data which can be derived from state (selector logic) will make your life easier.

In closing

These are but suggestions for those of you racking your brains across the “Redux” by choice or by waterfall. Let me know if you have any thoughts or are years beyond us and know if these approaches hit a wall.

I’m still learning too!

Also didn’t have time to write a shorter article, apologies for that.
🐧

--

--