Last week I shared the logic and components to view a sorted list of items from a shopping list SPA that I’m designing. At that point I had been focusing on designing the React/Redux front end only, using seeded data I’d provided in the code. This week I’ll outline some of the decisions I’ve made with regards to calls to the Rails API that I’ve set up to house the Postgres database. This is more of an exploration than a concrete set solution.
Here are some of the features I’ve decided that I want for the shopping list:
- A User can create a new named List.
- A User can add List Items to the List.
- List Items will include a Category so that items can be displayed by category.
- The new List Item entry form doubles as a search of items already in the users Master List (items from previous lists) that can be selected. New List Items will also have an optional quantity entry. List items must be unique.
- If a new List Item is not previously in the Master List, the item and its Category will be added to the Master List as well as the current List.
- The sort order of the Categories can be adjusted by the user based on the layout of the user’s particular super market, creating a logical shopping flow. As category sort order is adjusted, existing lists should adjust dynamically.
- Item category’s in the User’s Master List can be adjusted (dynamically adjusting items on existing lists). Master List items can also be deleted, but theses items will also be deleted from existing lists.
- Current List Items can be clicked on to mark as done.
While designing the frontend, I relied on three arrays of data: Categories, Master List of items, and a Current List of Items. As I began to really think through the needs, it was clear that the tables in the API were going to need to be a bit more nuanced approach as we cannot store arrays in a SQL relational database. (I plan on trying a Mongo DB build in the future to see if this is a good case for using a NoSQL DB).
I decided on five models/tables:
- List — user_id and a name.
- List_Item (items from a specific List) — list_id, item_id and a quantity.
- Item ( the user’s master list of items, not attached to a specific list) — item_id and a name.
- Category —user_id, name and a sort_order.
After the user initially logs in successfully the client initiates a request to the API for all data necessary to work with the user’s current list, including: User info, categories, master list of items, the current list and current list’s items. Something from all five tables is saved to the Redux store. With this info the user can now view and edit the current list as well as edit the category order and master list.
At this point the a user’s lists are not intended to be editable by several users at the same time which, if I understand correctly, would require a web socket solution. The easy, naive solution for synching DB and Redux would be to send the updates to the endpoints of the API and then reload data from the server. This has several downsides:
- Constantly refreshing data uses more bandwidth.
- This can create a lag in rendering updates (especially if connection is spotty)
The better solution seems to be to send the request, and then after the POST succeeds, dispatch the actions with updates to the Redux store locally. We could reverse the flow (update Redux first, then send changes) which would allow things to continue, even without a connection, but in this case, I have validation happening on the server side, which might lead to the two single sources of truth being out of sync. Below is an example of an action that adds a new item to the master list of items, and after dispatches the action to add the item to the current list:
Another challenge arises when trying to update the sort order. I’ve designed a drag and drop list to re-order what order categories are displayed, depending on the layout of your particular market. The new order needs to dynamically re-render the list of items.
An array of categories, each with a sort order attribute, is stored in state. The return from the drag and drop is a new list with the category array re-sorted. We can’t simply copy the new array into the global store because the underlying original category objects are still be referenced, a big no-no in Redux. Instead, I create an array with just the category ids sorted in the new order to dispatch to the RE-SORT_CATEGORIES reducer. This doubles as a solution for passing the new order to the API. The array with the order of category_ids is joined into a comma delimited string and sent in the request. In Rails, I split the string back into an array and iterate, changing the corresponding category’s sort_order to the new position in the database. Now Redux and the Postgres db are again synched.
This works well, but as a I worked through the solution, it came to me that storing the categories as a linked list in redux would probably be a more efficient way of reordering the objects. In addition, using a NoSQL DB like MongoDb would allow me to keep the categories as a collection when passing back and forth to and from the API.
Like I mention in the beginning, this posting is a bit of a thought experiment. Hopefully it’s piqued your interest enough to come up with your own solutions.