In creating my Recipe Box final project, I added features that quickly bulked up my Redux store with lots of extra state. One feature was a categories dropdown menu allowing the user to choose which category of recipes (i.e., "main dish" or "salad") they wanted to view; this resulted in a category variable in the Redux store along with a changeCategory action. Another feature was a dropdown menu allowing the user to choose whose recipes they wanted to view; again, this resulted in a selectedUser variable in the Redux store along with a changeSelectedUser action. The pattern was clear; anytime I wanted to add another way to filter recipes, I'd have to keep track of that information in the Redux state ... or would I?
As I was reviewing my code with an instructor, he suggested incorporating nested routes into my code and that idea immediately clicked - of course that's how I should be keeping track of the category, the selected user, etc. All of that information should be encoded in the route itself, not stored in Redux!
Inside of index.js, my App component iss wrapped in Router. Inside of App, there are only a few top-level paths inside of the Switch component available for when a user is logged in: "/manage-recipes" (for a user to delete their own recipes or notes), "/logout", "/search" (for searching the recipe database), and "/". I intentionally did not specify that last path to be an exact one because I wanted it to be triggered by both the root route "/" and the recipes route "/recipes". When the "/" path is matched, it triggers a rendering of the RecipesContainer component.
In order to achieve nested routes, RecipesContainer then has its own Switch component. We'll examine one of its Routes, "/recipes/users/:id". When that path is matched exactly, the RecipeCards component is rendered with router props and is passed an assortment of other props -- one of which is the recipes belonging to that selected user (i.e., the user whose id matches the path variable :id).
When RecipeCards is rendered, it only renders a RecipeCard for each of the recipes indicated here. In this way, the user can view any other user's recipes, the URL is more descriptive/restful than when it was simply "/recipes", and I am able to clean unnecessary information out of the Redux store. All in all, a win!
Monday, June 8, 2020
Sunday, May 17, 2020
Final Boot Camp Project ... now with React/Redux!
And so, five months after I officially started this journey, I've come to my final project for Flatiron School's Online Software Engineering program. When I think back to our first project -- a text-based game written in Ruby that runs through the command line -- it strikes me that the "programming" itself has not gotten any more complex. Programming structures (conditionals, loops, variables, etc.) are programming structures regardless of the language or the complexity of the project. The real difference between my final project and my first project is the technologies used to create it.
So, how did I incorporate these tools into my final project? My Recipe Box allows the user to create recipe cards with their favorite recipes from around the web, categorize the recipes for easy access, record any notes they'd like to remember for the next time they make the recipe, and see all their friends' recipes as well!
My main presentational component is RecipesContainer, which is responsible for displaying a set of recipe cards by rendering the RecipeCards component. RecipeCards, in turn, does some filtering of the recipes and then for each recipe, renders a recipe card. Finally, the RecipeCard component is responsible for displaying an actual recipe card, along with the user's notes about the recipe, a link to the recipe on the web, and a button for adding a recipe note. Of course, there are other components as well: a NavBar for navigation, a RecipeForm for adding a new recipe to your box, etc. Not to mention components related to user authentication, which was a challenge I had been avoiding but I'm glad I finally tackled!
Where does Redux come into play? When my front end sends an API request to, say, load all of the recipes from the database, those recipes get stored in the Redux store (i.e., the global state). The store keeps track of other information as well: all of the users, all of the recipe notes, the current user, whose recipes you are currently viewing, which category of recipes you are currently viewing, etc.
There were definitely some decisions involved in terms of which React components to connect to the Redux store, and I'm sure this is something that will change as I go back and refactor my code. For now, I chose to map most of my state and dispatch functions to props in my parent App component and then only connect to the store for more specific needs further down the chain. For example, RecipeCard connects to the store to grab all of the notes and the function for adding a new note (triggered by an "add note" button on a recipe card).
Overall, I learned a TON through this project and am excited to continue refining it so that I can release it to the world (or at least a small circle of friends)! Feel free to check out my repo here, and thank you again for continuing to join me on this journey!
For my final project, I continued on my quest to create the perfect recipe management app (basically, an app that I wish I had for managing my own recipes!). For the back end, I built a custom JSON-serving Rails API (similar to the last project). I created the front end using JavaScript and the ReactJS libary, along with Redux for state management and Redux-Thunk middleware to handle asynchronous fetch requests to the back end. I also utilized the React Bootstrap library to make my styling a breeze!
Some New Tools: React and Redux
I must admit that I was not excited about starting React. I am someone who really likes to understand exactly what is happening at each moment my code is executing, and so "vanilla JS" really appealed to me. Call me crazy, but I really liked manually updating the DOM to reflect exactly the changes I wanted to see. While other folks seemed mesmerized by "React-magic", I felt a little uncomfortable with not having hard-coded all of the DOM changes myself.
As I started to work more with React, though, I started to see the beauty of having my code organized into components. It gave structure and order to my JavaScript code where coding in JS had previously felt a bit like the wild west. In React, the App class is essentially the parent component that's in charge of all the visible content of the app. The building blocks of the React application are components, which are chunks of code that let you split the user interface into independent pieces. A particular component can then be rendered by its parent component, which can also pass down information to that child component (kind of like parameters when calling a function) in the form of props. A point that was emphasized over and over is that changes in props can only happen externally; i.e., if the parent component "remakes itself" and then re-renders the child component. A component gets re-rendered to the page whenever there is a change to its state or props.
Of course, as soon as I started feeling comfortable with React, Redux came into the mix! It was a little tricky to wrap my head around Redux at first, but like anything you just need to "feel out the contours and wait for your eyes to adjust" (an approximate quote from one of Flatiron's founders, and one that I have on a sticky note next to my computer!). Redux is a "state-management tool" that places all of our application's data in a single state ("the store"), which is just a plain JS object. To change our state, we need to create an action that holds information for how to update the state. Reducer functions take in a previous state and an action and return an updated state that results from applying the action to the previous state.
How does our React app connect to the Redux store? With a connect function that itself calls a particular function (usually mapStateToProps and mapDispatchToProps) whenever there is a change in the state. Whatever that function returns will get passed down to the "connected" component as props.
The purpose of Redux is to maintain a single source of truth for your application on the front end, as opposed to having different data structures in all of your JS classes that keep track of various information. While I was able to see this to a small degree in my own project, I imagine that a larger-scale project would really benefit from such predictable state management.
My Recipe Box
So, how did I incorporate these tools into my final project? My Recipe Box allows the user to create recipe cards with their favorite recipes from around the web, categorize the recipes for easy access, record any notes they'd like to remember for the next time they make the recipe, and see all their friends' recipes as well!
My main presentational component is RecipesContainer, which is responsible for displaying a set of recipe cards by rendering the RecipeCards component. RecipeCards, in turn, does some filtering of the recipes and then for each recipe, renders a recipe card. Finally, the RecipeCard component is responsible for displaying an actual recipe card, along with the user's notes about the recipe, a link to the recipe on the web, and a button for adding a recipe note. Of course, there are other components as well: a NavBar for navigation, a RecipeForm for adding a new recipe to your box, etc. Not to mention components related to user authentication, which was a challenge I had been avoiding but I'm glad I finally tackled!
Where does Redux come into play? When my front end sends an API request to, say, load all of the recipes from the database, those recipes get stored in the Redux store (i.e., the global state). The store keeps track of other information as well: all of the users, all of the recipe notes, the current user, whose recipes you are currently viewing, which category of recipes you are currently viewing, etc.
There were definitely some decisions involved in terms of which React components to connect to the Redux store, and I'm sure this is something that will change as I go back and refactor my code. For now, I chose to map most of my state and dispatch functions to props in my parent App component and then only connect to the store for more specific needs further down the chain. For example, RecipeCard connects to the store to grab all of the notes and the function for adding a new note (triggered by an "add note" button on a recipe card).
Overall, I learned a TON through this project and am excited to continue refining it so that I can release it to the world (or at least a small circle of friends)! Feel free to check out my repo here, and thank you again for continuing to join me on this journey!
Saturday, April 18, 2020
JavaScript Recipe Box Project
For our most recent bootcamp project, we were tasked with creating a single-page application that uses Rails on the back end and JavaScript on the front end. For me, this project had an especially steep learning curve. I remember just 10 days ago feeling completely confused by what I was supposed to do. I thought I had understood what the terms back end and front end meant, but in actuality I did not. I had no idea how to organize my JavaScript code other than in one index.js file, and I didn't really understand how to use a fetch request. Although I still have a ton to learn, I now feel like I have a much better grasp on all of those concepts.
I created a Recipe Box application, where the user can store their recipes from around the web on recipe cards (with links and images). The user can also add notes to a recipe -- for instance, allowing them to remember that those chocolate chip cookies need more salt next time, or that the zucchini frittata needs an extra 10 minutes of cooking time over low heat. And, in my favorite piece of functionality, there is a weekly menu planner on the right side of the screen with boxes corresponding to each day of the week. The user can drag a recipe card over to the menu planner, allowing them to plan out their meals for the week.
Here are some of my main conceptual takeaways from the project:
This was the most complex and gratifying bootcamp project so far; I'm proud to have created a web app that I actually want to use and am going to attempt to host this one so that others can also use it! Thank you again for joining me on this journey, and feel free to check out my GitHub repo here.
I created a Recipe Box application, where the user can store their recipes from around the web on recipe cards (with links and images). The user can also add notes to a recipe -- for instance, allowing them to remember that those chocolate chip cookies need more salt next time, or that the zucchini frittata needs an extra 10 minutes of cooking time over low heat. And, in my favorite piece of functionality, there is a weekly menu planner on the right side of the screen with boxes corresponding to each day of the week. The user can drag a recipe card over to the menu planner, allowing them to plan out their meals for the week.
Here are some of my main conceptual takeaways from the project:
- Front end v. back end: Prior to this project, I would have said that the "front end" refers to the aesthetic aspect of a web app (how it appears in the browser), and that "back end" refers to all of the functionality. I quickly learned that this was wrong! Functionality is a broad term and some functionality does, in fact, live in the front end. Clicking on a button to make a form appear is a front end feature because it requires no communication with the server. Clicking on a button to save a new recipe, on the other hand, does require persistence to a database and thus communication with the Rails server, making it a "back end" feature.
- Classes on the front end: It was quickly apparent that my JS code was a mess, but I wasn't too sure how to fix that. When we created our Rails project, the separation of concerns was much clearer and it was easier to decide what code went where (in fact, Rails didn't give us much of a choice!). In JS, it's possible to keep all of your code in a single index.js file -- which of course results in disorganized code that is difficult to maintain. I decided to mirror each of my back end model classes (i.e, recipe.rb) with a front end class (recipe.js) that is responsible for the front end functionality related to those database records. As an example, the recipe.rb class on Rails side is responsible for performing validations and title-casing recipe names before a new record gets persisted. The recipe.js class on the front end is responsible for loading all of the recipes from Rails, generating the HTML for each recipe's card, and then rendering that HTML to the DOM.
- Updating the DOM v. updating the database: I was incredibly excited to get my drag-and-drop feature working, allowing the user to drag a recipe card into the weekly menu planner to organize their meals for the week. (This was a very useful drag-and-drop tutorial!) I spent way too long admiring the fact that the recipe name would *magically appear* in the menu planner ... and then I realized that as soon as I refreshed the page, it was gone! Yet again, a lesson in the difference between updating the DOM versus updating the actual database. In this case, I actually needed to create a new database table to keep track of the days and their associated recipes. I created a save button that triggered a PATCH fetch request, updating each day.recipes array in the database.
This was the most complex and gratifying bootcamp project so far; I'm proud to have created a web app that I actually want to use and am going to attempt to host this one so that others can also use it! Thank you again for joining me on this journey, and feel free to check out my GitHub repo here.
Sunday, March 22, 2020
Rails Project: What's for Dinner?
If I'm honest, Rails was the most difficult unit for me so far. I really, really liked Sinatra. I liked that at the end of my Sinatra project, I had a functional application and I could literally explain every line of code. I knew what it did, and I felt comfortable experimenting by changing it. As soon as we switched over to Rails, I felt like I no longer understood anything! Sure, it's possible in Rails to generate an entire app with basic CRUD functionality in seconds ... but what the heck is actually happening??? I found that to be a theme with Rails. Even now that I have finished my project, half of the files are still a big mystery to me. What exactly does database.yml do? What are all these initializer files? I've been told that this is the thing about Rails, that there's always more to learn and understand, to be patient ... but still, this is outside of my comfort zone. I like to understand all of the nuts and bolts.
Which is not to say that I'm not proud of the project I built! I called my application What's for Dinner? and it's designed to solve a most basic need of parents everywhere: finding kid-friendly weeknight dinners. Users can submit recipes, view recipes, filter recipes by category, search for recipes, and save recipes to a "recipe box" (as long as they have an account). They can also sign in through Google! (Let me tell you, after the headache this feature gave me, I will never take for granted being able to do this on any site I visit, ever again!)
Rather than walking through the entire design, I'll focus on a key understanding I gained through the process of building my project:
Nested Forms
These have been my biggest obstacle in Rails -- specifically, understanding all of the different form macros (form_for, form_tag) and using them to generate forms for nested attributes. After much trial and error, I think I finally understand the beauty of form_for!In my domain, a Recipe has many RecipeSteps (something like recipe_id: 15, step_number: 1, content: "Boil water") and RecipeIngredients (a table joining ingredients to recipes, something like recipe_id: 15, ingredient_id: 12, quantity: "1 cup"). When a user wants to create a new recipe, I first ask them how many steps and ingredients their recipe has (an annoyance I hope will no longer be necessary once we learn JavaScript). I then use my RecipesController#new action to build a new Recipe and build the correct number of RecipeIngredients and RecipeSteps associated with that Recipe object:
Then, in my /views/recipes/new file, I can use fields_for to capture data for those nested attributes (RecipeIngredients and RecipeSteps). Note that recipe_form is the form builder object defined at the start of my form_for; i.e., <%= form_for @recipe do |recipe_form| %>
So what happens when the submit button is clicked, triggering the RecipesController#create action? Well, a new Recipe record is generated and then there is an attempt to save it to the database. But, this simple-sounding step - "a new Recipe record is generated" - is actually quite involved! In particular, the Recipe class has custom setter methods responsible for creating the associated RecipeIngredient and RecipeStep records while creating the Recipe object they belong to:
And that's that. Now that I've talked through it from start to finish, I wonder why it ever seemed to mysterious!
Thanks so much for following me on this coding journey. Please feel free to check out the repo for my Rails project here; I'm also hoping to "go live" with this project one day soon (once I've made some upgrades, of course!). Comments and suggestions are all welcome!
Thursday, February 13, 2020
Sinatra Project: Practice-It
I hesitate to say that I have "completed" my second project as part of Flatiron's Online Software Engineering Bootcamp. There are still some unresolved issues relating to connecting to a local host, and I have done absolutely no styling. (Starting to learn some CSS is on my agenda for break week!) However, I can proudly say that I have created an application that works and solves a real-world need of some sort. But, before we get to that...
This was also been an exciting month because I've started to learn something that's totally new to me, which is how the internet works. I now feel comfortable with the idea that the internet functions because of conversations between a client (i.e., browser) and a server (the code that runs a specific website you're trying to load). Essentially, a client makes an HTTP request, the server runs some code (maybe code that future-me will write!) and sends a response, and then the client (browser) renders that response string into something visually appealing.
As we started to see how basic Sinatra apps are set up with models, views, and controllers, one of the big mysteries of my life became a little less foggy. When I taught CS, my kids would write programs in Java or Python that lived on their computer and could run through an IDE. They'd often ask me how you create websites that other people can interact with and allow your code to live somewhere other than your computer, and that's honestly something I was never able to answer (which is part of the reason I decided to go to boot camp!). I love the fact that I'm starting to gain a basic understanding of that process, and it's really the MVC framework that has been helpful for visualization:
I had a great time with this project, as it cemented for me a lot of the content we had been learning. It was also fun to think about an app from the perspective of a teacher user, since that's literally what I was until I started bootcamp!
It's hard to say for sure since I'm still knee-deep in the process, but I think I may have begun to clear a few hurdles on my way to becoming a software developer.
Reflections on My Learning:
Databases were a big "a-ha" for me because all of a sudden I could imagine how companies store data related to all of the objects they create in the course of a day -- it's probably not in class array variables, but rather in tables that live in a database somewhere. Databases also solved the mystery of how information gets persisted, and further solidified my understanding of classes and objects. (In fact, I wish I had this analogy when I was teaching AP CS. There are some students who are confused about classes and objects all year, despite the 5,000 analogies you come up with. I actually think that the relationship between objects and databases -- that a table represents a class, and a row in that table an object from that class -- is one of the most student-friendly explanations I've heard!)This was also been an exciting month because I've started to learn something that's totally new to me, which is how the internet works. I now feel comfortable with the idea that the internet functions because of conversations between a client (i.e., browser) and a server (the code that runs a specific website you're trying to load). Essentially, a client makes an HTTP request, the server runs some code (maybe code that future-me will write!) and sends a response, and then the client (browser) renders that response string into something visually appealing.
As we started to see how basic Sinatra apps are set up with models, views, and controllers, one of the big mysteries of my life became a little less foggy. When I taught CS, my kids would write programs in Java or Python that lived on their computer and could run through an IDE. They'd often ask me how you create websites that other people can interact with and allow your code to live somewhere other than your computer, and that's honestly something I was never able to answer (which is part of the reason I decided to go to boot camp!). I love the fact that I'm starting to gain a basic understanding of that process, and it's really the MVC framework that has been helpful for visualization:
- Models are responsible for interacting with the data. In our Sinatra apps, these were written as Ruby classes, each of which was connected to a table in the database. The model classes were where we indicated associations between our objects (i.e., belongs_to and has_many), which are basically ActiveRecord macros that give us even more methods that we can call on objects from those classes.
- Controllers are the middle-men. They contain routes that match HTTP requests (i.e., GET '/posts/new', which might result in a form for a new blog post displaying). Based on the request, the controller runs (Ruby) code that might redirect to a different route or render a view (something the user will see in their browser, like a form) using some information retrieved from the database.
- Views are the front-end of your app (i.e., what the user will actually see on their screen). They are written in HTML and although they may display information from the database, they did not retrieve that information themselves! Instead, the "middle-man" controller got the necessary information and then passed it over to the view.
And in this way, we created a Sinatra app that is basically a lovely dance wherein the browser makes a request, the server processes the request and implements some logic in order to determine which view to render, and then digs into the database to find what it needs -- and then finally renders a view, which is all that the user will ever see. And this happens again and again. As I now visit websites in my everyday life, I'm amazed at how complicated -- yet how simple! -- it all seems. Complicated because I'm sure there are thousands of lines under the hood giving everything its functionality. Simple because, at the end of the day, it's all just HTTP requests and responses. Which finally brings us to the app that I created for my Sinatra project!
Reflections on Sinatra Project:
I was trying to come up with a "real-world" problem I could solve with my app, something that required managing a lot of content and giving users a nice interface for interacting with that content, when it hit me! As an AP teacher, I always had the following problem when it came time to reviewing for the AP exam: there are a ton of practice questions available for every combination of topics in the course, but it's really hard to keep track of which students have completed which questions. There is lots of other functionality related to this, like being able to easily access all questions related to a certain topic, or view all students who have completed a given question when you're looking for a good "peer tutor" for another student. So, I decided to create Practice-It, a Sinatra app for making an AP teacher's life easier during those critical review weeks.I had a great time with this project, as it cemented for me a lot of the content we had been learning. It was also fun to think about an app from the perspective of a teacher user, since that's literally what I was until I started bootcamp!
It's hard to say for sure since I'm still knee-deep in the process, but I think I may have begun to clear a few hurdles on my way to becoming a software developer.
- Workflow: I started to hit my stride in terms of an effective workflow. I set up a notes file in my project, which I used to record and categorize features I wanted to implement. I'd pick a feature, write a controller route that rendered a dummy view file, and then finally work on getting the correct HTML in the view file. After each component appeared to work even a little, I'd make a commit and push my code to GitHub. I'd then test the route out in the browser and use lots of binding.pry's to inspect what was going on from the console. After however many iterations that took, I'd make another commit, update my notes file, and then choose a new feature to work on.
- Reading documentation: I spent a lot of time on Rails Guides learning about various ActiveRecord methods, as well as getting un-stuck on Stack Overflow. Stack Overflow used to intimidate the heck out of me -- I'd literally feel my anxiety level increase anytime I clicked on a post. I feel like our relationship is finally improving and, although I still certainly have trouble parsing through some of the responses, I generally feel like I can get what I need from it.
- Thinking about object relationships: Before this project, I had a basic understanding of object relationships (we focused on "is-a" and "has-a" in AP CS) as well as some of the ActiveRecord associations, like belongs_to and has_many. My project really clarified things like why I would ever need a join table (in my app, a student has many questions and a question has many students -- so I needed a join table to keep track of all (student, question) pairs) as well as the importance of thinking about all aspects of your program. For example, in the spirit of collaboration among teachers, all teachers in my app have access to all of the questions. However, every question has an "owner" who created it, and only that owner can update or delete a question. It was only after lots of fiddling around with various use cases that I came upon the question: what should happen to the questions a teacher "owns" when that teacher deletes their account? As a simple solution, I reassigned ownership of that question to another teacher in the database -- but it did get me thinking about some of the subtle questions that must come up when you've got lots of models with plethora of associations among them.
My next thought is to dive into CSS a bit so that I can make the user experience more visually appealing; I'd also love for teachers to be able to import student and question data via CSV files rather than having to manually enter it all into forms.
I'll leave it at that for now, and invite you to check out the Practice-It GitHub repo. I'd love your feedback and ideas, and I really thank you for joining me on this journey!
Subscribe to:
Posts (Atom)