I’ve been adding a list sharing feature to my Cart Compass app that required me to restructure how an individual list item object was structured. Initially the item object was linked to a master item, the master item part of a master list of items linked to a user’s account, items that are remembered and called up when building future lists, with the name and category embedded. My initial plan was to link items to actual lists with this master item. In this way, when the fields of the master item were edited, the instances in lists would dynamically update.
All well and good, except that this meant when accessing a shared list from another user, not only the list’s items would need to be accessed, but the original user’s master list items would also need to be available. It lead to some interesting questions.
When a user adds an item, should it update their master list item as well as the originating user’s master list?
Should the originating user’s master list be permanently cloned in its entirety to the shared user’s master list, or only the items contained in the original list?
In the end, I came to the conclusion that, the small bit of flexibility gained by keeping the reference wasn’t worth the complications that would need to be added in order to easily share lists. It made more sense to, after choosing an item from the master list, to just hard code the item’s name and category ID when creating new items for a specific list. Continuing to reference the item’s category ID (an ID that is universal to all users) in the item object allowed the user to still dynamically re-order the lists, which was the most important feature of the app anyway.
Reaching this decision, I set about refactoring both the back and front end to accommodate the new model. This would involve making changes in the following areas:
API (Express.js, MongoDB, Mongoose)
- the item model
- the add new item and change item status routes
- the add item to the master list route
- the item custom types
- doAddItemToCurrentList — change MasterItemId to name
- doRemoveItemFromCurList — change itemId to name
- doChangeItemStatus — change itemId to name
In addition the logic in the components that create and display the lists needed to be refactored as well.
With this app, I’ve been very conscious of code organization and clarity, trying to focus on separating concerns, commenting efficiently and effectively, and naming variables and files meaningfully.
Here’s a list of best practices that I try to follow:
- Organize files logically, using folders to group files that perform the same function.
My Express/Mongoose API has two folders that hold most of the meat of the server — a models folder that contains model definitions and schemas for defining the structure of the Mongo database and the routers folder that contains the Express routes. Inside the folders each file is named with the corresponding name of the model.
The client is built with React/Typescript/Redux. Folders likewise group files by their functions. Inside the main src folder is a store folder contains subfolders that are named after the four main reducers in the redux store: System, Lists, Master lists and Categories. Inside each of these folders are three files: reducer.ts, action.ts and types.ts. Inside of src there is also a components folder housing the react components.
- Name folders and files thoughtfully. This seems fairly obvious, but worth reiterating. I find that as the project progresses my file names need to be adjusted to accurately describe their functions, especially the components. It’s worth going back to make sure names still make sense. Changing React file names in VSCode will automatically adjust references to the renamed files throughout your code, so the process is pretty painless.
- I have moved towards longer, more descriptive variable names lately. With auto completion, it really doesn’t create much more work and having variable names that accurately reflect the purpose and are consistent will others (or even yourself 6 months from now) quickly capture what’s going on.
- Separation of concerns refers to the practice of limiting the responsibility of blocks of code to one simple and obvious function. I have component that prepares data for displaying a list of items. The items need to be retrieved from state and sorted by categories before being sent to the child that displays the individual categories which then display the individual items. Instead of building one large function that accomplishes the task, I break the task into two smaller functions: divideListByCategories and makeLists. Only one of these functions is actually effected by my refactor. Because it’s a small block of code, it’s easy to identify what needs to be changed and to isolate problems, should they be introduced.
- Use comments sparingly but with intent. A comment should help define the overall intent of a section OR illuminate code that may not be obvious at first glance.
Although the refactor in question required making adjustments to code in ten files, because I had employed some basic good practices, the effort was minimal and I was happy to discover that, after fixing a couple of obvious errors, both the API and the front end fired right up, functioned as before, and I was ready to add the sharing feature with my new item model deployed.