Redux By Example: Part 4

This article is part of a series (starting with Redux By Example: Part 1) of articles that, through a series of progressively more complicated examples, illustrates a number of core Redux concepts. These examples are provided as Node.js applications; without any browser or React complexities.

To build:

  1. Download and extract the repository Redux Patterns (download again if you had so before).
  2. From the extracted folder, run npm install
  3. Run the command ./node_modules/.bin/babel src -d dist

Selectors Broken

Recalling back to earlier examples, we designed the reducers to be pure functions so that we could efficiently detect changes in the state by simply comparing references.

In the previous Selectors example, we introduced a function getItems to encapsulate the implementation details when responding to events; it returns an array of items. It is this function that we used to access the state; not leaves of the state directly.

const getItems = state => state.ids.map(id => state.byId[id]);

Looking at this function, we can see:

  • Changes in state.ids results in a new array; so list additions and removals are detected.
  • Changes in state.byId results in a new array; so changes of list items are detected.
  • In fact, because the map function always creates a new array; this function will always report changes (even if neither state.ids or state.byId change).

Run the example with the command node dist/selectors-broken.js .

Looking at the example’s source code:

  • Comparing the resultant arrays after the execution of the fetch and update action creators properly reflect the change in the array.
  • Comparing the resultant arrays after the execution of the the BOGUS event, however, also reflects a change in the array.

Because the state, in real applications, will likely have many leaves, using the result of selectors like getItems is a real problem. Basically, with any state change, it will appear as if the list has changed.

Reselect

When I started with Redux (with React), it took me several months before I noticed the problem with the selectors like getItems; and then it was only by accident. Part of the problem was that I was closely following Dan Abramov’s (developed Redux) patterns in his tutorial; so I assumed that I was doing things correctly.

Turns out that the Redux team (specifically, Mark Erikson) addresses this issue in the FAQ; we will follow his advice and use the reselect module.

To simplify things, I will summarize what the reselect does without using complicated terms like memoization (I had to look it myself).

In our previous example, the getItems function depends on two variables state.ids and state.byId to generate the resultant array. The gist of reselect is that we wrap the implementation of getItems with another function that caches state.ids , state.byId and the result of getItems. When this function is called later it compares the references of state.ids and state.byId to its cached copy; returning the cached result of getItems if they are the same (otherwise executing getItems, caching the result, and returning the result). The actual implementation is:

const getItemsIds = state => state.ids;
const getItemsById = state => state.byId;
const getItems = createSelector(
[getItemsIds, getItemsById],
(itemsIds, itemsById) => itemsIds.map(id => itemsById[id]),
);

note: The extra functions getItemsIds and getItemsById are necessary for the reselect implementation as it compares the the return values of these functions.

Run the example with the command node dist/reselect.js .

Looking at the example’s source code:

  • Comparing the resultant arrays after the execution of the fetch and update action creators properly reflect the change in the array.
  • Comparing the resultant arrays after the execution of the the BOGUS event, however, does not reflects a change in the array.

Ducks

As your application grows, you will find that you will have a lot of leaves of the state tree with their respective functions: reducers, action creators, and selectors. While there are a number of ways of organizing this code, I found ducks as a simple solution.

Asynchronous Actions and Thunks

In the previous examples all of the action creators synchronously returned an action (JavaScript object) that gets fed into the dispatch function. For asynchronous actions, e.g., fetching data from a server, one common pattern is to use a more complex action creator pattern called a thunk (this is how Dan Abramov addressed the issue in his tutorials).

The use of thunks is somewhat complicated (likely could fill an another several articles); I will defer to Dan Abramov’s material on it.

Wrap-Up

If you have made it this far in the series, you have a sufficient foundation to effectively use Redux in your applications.

I also maintain a boilerplate that I use for starting new React / Redux projects that uses the Redux patterns that have been covered in this series; including ducks and thunks.

I hope you found this series helpful.

Addendum

Since I wrote this series, I was made aware of another library (redux-orm) that addresses several of the nagging pain-points that I have had with Redux:

  • Writing similar complex chunks of code (reducers, selectors, and action creators) for different data types.
  • Handling relationships between data types.

The series of articles, Practical Redux, by Mark Erikson provides an excellent discussion on redux-orm as well as a number of other practical topics.

Show your support

Clapping shows how much you appreciated John Tucker’s story.