This app was built to be a meal planning assistant. It keeps an inventory of your food, a list of your own recipes, and your grocery purchases. This project has been a great way for me to try out new technologies and has gone through several iterations over the years.
The ingredients in dishes have a great deal of flexibility. You can make things optional, list substitutes, and specify another ingredient that this item “counts as” (e.g. your sandwich dish has “cheese” as an ingredient, but you have “cheddar cheese” in your fridge; for this situation you can make cheddar cheese fulfill the requirement for “cheese”). Substitutes like that are taken into account when filtering dishes on whether or not you have all the required ingredients.
Additionally, you can use other dishes as ingredients. This is helpful for things like homemade sauces that go into different things, or other meals that are assembled, like sandwiches or burrito bowls. It was essential to include this feature in order to avoid messy duplicates across dishes and ingredients and to keep the data more true to its source.
These functionalities drastically changed the original database design and added a significant amount of complexity to the business logic, but greatly increased the overall usefulness of the system.
I used to be terrible at figuring out what to cook on the spot. I’d end up in a last-minute scramble every single day, and it was a huge source of stress for me. I became obsessed with avoiding that stress, and dove deep into meal planning. I planned out every meal as far in advance as I could and made sure I had maximum variety from day to day. I ended up constantly buying ingredients for every dish under the sun, and I couldn’t keep track of them and use them up in time. Things went bad, and I’d kick myself for it.
The obvious solution to me was, “I need a database of every food item in my house.” And almost immediately I started having big dreams of how everything could fit into a system that could tell me anything I wanted to know. What can I make that doesn't use chicken, and isn't a soup, that I haven't had for a while? What do I need to use out of the fridge ASAP, and what can I make with it? Have I been spending too much on eggs when I could be buying them cheaper elsewhere? More important than all of these questions was how I could do more with less. I was spending so much time and energy on perfecting my process and I wanted everything to be automated. I wanted to be able to switch my brain off and, ideally, create something that could do it all better than I could.
In a couple of places in the app you have a big list of items or dishes that you can manipulate in different ways: you can sort it by different fields, you can apply filters, and you can do a search. You want these all to be applied at once; for instance if you have a filter active, you want to be able to search within the filtered results. It was a really tricky thing to implement, particularly with handling state and the filtered copy of the original data. When I tried to do multiple things with the list, running different sorts and filters on this state variable, I very quickly got into infinite rerenders. There’s probably a much better way to solve this, but I ended up using a single useEffect that runs on every search and every filter change and sorting change, and checks against each of them.
Another challenge I ran into was handling dates. Using gqlgen, your models are generated from your schema and they’re strongly typed. So dates coming back from the database as a date type must be a string in the resolvers, and you need to decide how you want that string formatted, whether it’s the number of milliseconds or something else. I messed around with changing the data type in different places, and each caused its own set of problems. In the end I modified my queries to cast the dates to text, so the conversion is made as early as possible. This has worked out for me so far, but I'll be keeping it in mind during future changes that might cause data loss or other problems.
When you're inputting an ingredient in a recipe, or logging the purchase of an item, you want that item to be from the single source of items that already exist. You don't want to have a bunch of names that mean the same thing. "cheese", "cheddar cheese", "cheese, cheddar". That sort of thing. You want to essentially pick from a list. But when your list of every item ever gets in the hundreds, you really don't want to have to scroll through a dropdown. A <datalist> was an ideal solution for this problem because you can just type in what you're looking for and see what already exists.
The other key benefit is that if you need to add a new item, you're not restricted to the available options, the way you would be with a <select>. You can enter in the new name as with a regular input, and the app will add that item to the database along with whatever dish or purchase item you're creating.