Designing a Sorted List with Sub-headers

Rick Glascock
5 min readSep 12, 2020

Like for many of you, the process of solving a coding challenge provides me a rich learning experience. It’s rarely a straight path, but the deviations, the ‘rabbit holes’, while not included in the final solution, frequently lead to a deeper understanding of the principals of coding. My latest project, and a particular (seemingly innocuous) feature I chose to add, leads me to this week’s exploration.

Photo by Peter Wendt on Unsplash

We’ve has been stuck at home the past months. Sound familiar? Cooking has become an important part of our lives. The first few weeks dinner time would arrive and we’d all stare at each other. No one had taken the initiative and there was nothing prepared and no restaurants to fall back on. Finally, we set a schedule so that each of us plans and cooks dinners for a full week. With three of us, this means one week of cooking intensity and two weeks of sitting back and enjoying the good eats (the dirty dishes are another matter). The outcome has been a marked improvement in the quality and variety of food we’ve been eating.

When my turn comes around, I like to plan menus for the whole week and on Monday morning do all of the shopping so ingredients are at hand. Shopping is not high on my list of favorite activities so I like to get in and out fast. To help get me through, I decided to build an SPA shopping list that lets me enter in ingredients and quantities, and then display the sorted items on a list sorted by store section. With the sorted list, I can move through the sections quickly, confident I won’t have to return.

While the app will ultimately have an API to store lists, a master list, and allow others to login and use the app, I decided to focus on the React frontend first, using some seeded data to design and test. The interface is simple: a nav bar on top, two columns underneath — the Add Item form on the left and My List on the right. As you add items the app remembers the new items and brings them up as suggestions for next week’s list.

The UI

The challenge came as I began to code ‘My List’: in particular, figuring out how to pull the headers from the sorted list. Here are a couple of solutions I came up with:

Build a custom sorted object each render of the list

In this solution I take a basic array of item objects stored in the Redux store, and place them in an object with key : value pairs where the key is the category number (there is a lookup associating the category number with the actual category label) and the value is an array of items with equal categories.

const sortList = () => {
const sorted = {}
const divs = [];
// separate items into category objects curList.forEach(item => {
if (!sorted[item.category]) {
sorted[item.category] = [item];
} else {
sorted[item.category].push(item);
}
})
// iterate through each category object creating a <ListGroup/> component along with the category label and specific items for that category. for (let group in curList) {
divs.push(<ListGroup
category={group}
items={curList[group]
key={group} />);
}
return divs;}

The <ListGroup/> component displays the category header and builds the individual <ListItem /> components.

This solution gets the job done, but I was concerned that the object has to be re-built every time the list is re-rendered (granted, not a deal breaker with such a small list), so I tried something else:

Building the sorted object directly in Redux store

In this solution I build the sorted object directly in state as I dispatch new items to the reducer.

case 'ADD_TO_LIST':newList = Object.assign({}, state.curList)
if (newList[action.payload.category]) {
newList[action.payload.category].push(action.payload)
} else {
newList[action.payload.category] = [action.payload]
}
return { ...state, curList: newList }

This solution worked as expected, building the object of categories in state which I then can access when building the ListGroups without having to build the sorted object every render. However, I discovered unexpected consequences down the road.

As it happens, I’ve included a boolean ‘status’ property in the items to allow users to cross out items they’ve already found. Clicking on a list item should dispatch an action that toggles the status. The problem is that reducers in Redux should not mutate state. Actually, Redux won’t throw an error if you mutate state directly. But, because old and new states are compared at a shallow level, mutating rather than replacing state won’t necessarily be registered as a change of state, and therefore components won’t know to re-render. Data is changed or added, but not reflected with an updated view. The problem is compounded when trying to modify a property that is deeply nested in state — in my case now, trying to update the property of an object (the item) within an array of items within an object (the category) within state. This is a hurdle that is inherent in Redux and is the reason that the Redux developers recommend keeping your data structure as flat as possible.

A third approach might be to do a simple sort of the items array by category and then at time of rendering, iterate through the array, checking to see when we reach a new category and rendering a new header. I think this approach would be the most susceptible to bugs caused by improperly formed data.

In the end, I chose to go with the first solution, although I’m sure there are other (maybe better) options out there. While this particular challenge was seemingly trivial, it allowed me to gain a better understanding of Redux, and more generally, how to work with objects in javascript. Our growth, as programmers, and humans, comes out of pushing ourselves to explore different options, sometimes the road less traveled. In doing so we gain knowledge and confidence, and exercise our ability to be creative. Bon Appetit!

--

--

Rick Glascock

After years of teaching music in Austin, Shanghai and Yangon, I’m making a career change to my other passion, software development.