Too Blessed to Be Stressed
My final project for the Flatiron bootcamp is a website called Blesstaurant, where users can search for vegan restaurants the world over, and once logged in can rate establishments and leave reviews. The project was a culmination of everything that I had learned at Flatiron, and I pushed myself to use as many of the tools and as much of my knowledge as possible that I had been taught thus far..
The back end was done with Ruby on Rails, with BCrypt for hashing and salting passwords. In addition, I made a custom serializer rather than using Fast JSON API or Active Model Serializers. The front end was React, coupled with Redux, Thunk middleware, and JWT web tokens for authentification. There are many aspects of this project I could discuss, and it left me stymied as to what to write about. And then I saw the springboard prompt…
Challenge accepted! Below I will explain how the way this project unfolded can used as a metaphor for life, and how it can be likened to techniques for success.
- Taking time for preparation and proper setup saves time in the long run:
While it may be a hackneyed old chestnut, the adage “A stitch in time saves nine” definitely applies to life in general, and React/Redux in particular. Initially, since my familiarity of Redux was not so deep, I wanted to jump right into my project with the methods I had become accustomed to using React by itself. Skipping Redux setup, I could get basic features up and running, and have the satisfaction of some progress with getting a few things on the screen relatively quickly.
Instead, I toiled away at the beginning setting up all the parts necessary to have Redux work properly. Importing modules, wrapping the App in a Provider, creating a store, writing a reducer, making my actionCreator… all these steps took a bit of time — and these steps were not fun and had no immediate dopamine rush of getting visible results rendered to the page.
Once this was all set up however, the ease of building my project and the time it took to pass data to and from all my components were improved greatly. No more writing callback functions to lift up state. No more passing props down again and again through each tier in the hierarchy. Just by importing {connect} from Redux in a component, I could map state or dispatch to props anywhere with minimal effort. { connect } was my wormhole to the Beta Quadrant, or my wardrobe to Narnia, transporting my data instantly across space and time.
Another practice which required some initial effort but ultimately was a huge time-saver was destructuring my props. Although my impetuous nature wanted to dive head first and begin using the passed-in data immediately, taking a just a few second to destructure made my code much more legible, and saved many keystrokes in the long run since I no longer had to write out this.props.nestedLayerOne.nestedLayerTwo over and over again.
Plus, if it was a functional component, I could destructure rather painlessly directly where props was being passed in as the argument/parameter to the function.
2. Be organized:
When an issue becomes too daunting, break it up into bite-sized manageable chunks. As my project grew, so did my reducer — its state was getting quite large and the ever increasing cascade of swtich/case statements was becoming taxing to keep track of. The metaphor here is my use of combineReducers. By dividing my reducer into three distinct parts that each handled just one model, my code became more intuitive and easier to follow, since each models’ concerns were separated and not co-mingled and all jumbled together.
3. Know where you want to go and how to get there:
The form to add a review was a modal, and the way it operated was dependent on a handful of factors. By lining those up so that the ternary statements were “chained” (!true ? do this first : !true ? do this second : do this third) rather than “nested” (!true ? !true ? do this third : do this second : do this first) made keeping track of the conditions and where I was in my logic chain much simpler.
First, should the modal be displayed or not? If the user is logged in, show the review form. If the restaurant is in the backend, just do a post to reviews. If the restaurant does not exist, first post to restaurant, then post to reviews with the id from the response of the first fetch. That’s it….or so I thought. Let’s explore below.
If displayModal mapped to state was true, declare a const “display” to “block”, otherwise set it to “none” (line 10). Then with es6 syntactic sugar I could show the review modal form by passing the const “display” into the div style.
If the user is not logged in, do not render the form, instead allow them to sign up or login (line 15–17).
Next when logged in users see the form, if the restaurant is not already in the back end (!restaurantId), addRestaurantAndReview would post to restaurants and then post to reviews. If it is in the back end, addReviewOnly would just post to reviews. (lines 22–24).
Nice and tidy, until I started beta-testing and found an issue. What if the user left the review field blank? The validation for that was in the review model, so the response errors would alert the user that it needed to be filled out. But when the user fills out the review and tries to submit again, the restaurant has already been created. So now addRestaurantAndReview will not run because restaurants are validated in the backend for uniqueness (single source of truth), and it was created on their first attempt which failed due to the review validation.
My first attempt at fixing this issue was to check to see if the review content was blank in the actionCreator where addRestaurantAndReview was defined, and if it was indeed blank, alert the user to the fact. However when I tried this, the whole system crashed when a user submitted a blank review. I am unsure of the reason, but I have a feeling it was due to the Thunk middleware and the asynchronous dispatching.
So I created another layer in the ternary chain, first checking if the content did not exist (!content), in order to alert the user and stop the action from dispatching. This worked somewhat, but it redirected the user to a new page with the params attached to the end of the url. Eventually after trial and error I figured out I could pass the alert into e.preventDefault to stop the page from redirecting (lines 20 and 21).
Another unexpected edge case I encountered during beta-testing was if I users added a review to a new restaurant (addRestaurantAndReview), and then tried to immediately tried to add another review. A similar issue to above happens, where the form tries to add the restaurant again but cannot due to its uniqueness validation. This was an easy fix to addRestaurantAndReview in the actionCreator. After posting to restaurants and reviews, also dispatch an action where the restaurant id is put into state. This way if they post a review two times in a row, the first time it runs line 23, and then all subsequent form submissions run addReviewOnly (line 24).
Navigating the conditional/logic tree and debugging the edge case errors was an exercise in knowing where to go, and determining which direction to turn each step of the way.
4. Have fun and be inspired
One of the goals of the final project was to take everything you had learned thus far and tie it all together, but also to expand on top of that in order to stretch your understanding. The idea of Blesstaurant was inspired by a good friend of mine whose nickname is “Ayo.” What better way to expand upon my knowledge than by adding a little easter egg hidden in the code in her honor?
The idea was that if a user held down the “a”, “y” and “o” key simultaneously anywhere in the website, a picture of her would appear. First I created a component with her image called EasterEgg, and imported it into App. Having it here at the top most layer means that it can be shown anywhere in the app.
After much googling and digging through stackoverflow, I found a few things but none of them seemed to work. onKeyPress was depricated. There were some complicated messy methods that included mapping the values in an array and checking to see if the conditions were true. I reasoned that rather than having the easter egg state be a boolean of true, it may be an easier work-around to hold an integer value and display the easter egg if it equaled the specific integer value.
When the component mounts, I add two event listeners to the document that change the value of the easter egg. One listener operates so that if a key is pressed down (“keydown” line 24), the value of state is increased by one if the key is either the “a”, “y”, or “o” key (line 42–44). The problem here is that if the keys are held down, the value keep incrementing by one, so I check to see if the event keeps repeating (line 41).
The other event listener waits for a key to be let go (“keyup” line 25), and if the keys are either the “a”, “y”, or “o” it will decrease the value by one (line 33–35). With these two functions always listening to the document, if and only if the “a” “y” and “o” are pressed simultaneously will the value be equal to three.
Lastly, if in fact the value of easter egg is equal to the magic number three, render the component to the page and show her image (line 54). Even though adding this little secret in was not required and took more time than I had anticipated, it expanded my understanding and was an enjoyable labor of love and fun little problem to solve.