Mahlzeit!
By: Monica Gerard
Tags: React, Redux

Mahlzeit! Landing Page

My final project for Flatiron, Mahlzeit!, uses React/Redux for the front end and Rails for the backend API. Presently, the app facilitates the process of searching for recipes on the Internet. One can search by recipe title (“Split Pea Soup”) or by ingredients that you have on hand (“beluga lentils, celery, garlic, carrots) or would like to include — it’s very flexible that way. You can also filter the searches by dietary requirements, such as gluten-free or vegan, such that the recipes that are retrieved only include ingredients that fulfill those requests. The search results provide information regarding calories and the number of ingredients to help you narrow down which recipes are the most practical to prepare. You can select a recipe to see the ingredient list and link to the original website the recipe comes from so you can get the preparation instructions. You can also save recipes that you want to return to. Presently, the app’s functionality is restricted to filtered searches and saving recipes. Eventually, the app will be expanded to include shopping list capability, photo storage, multiple users, preparation notes, reviews, ratings, personal recipes, and a recipe swap area.

I used the Edamam API to assist in searching for recipes. The trick is to correctly parse your queries and selections so as to return the specific recipes. I derived a search string from the documentation the API provides and figured out where I needed to pass through search parameters (query and healthOptions):

`https://api.edamam.com/api/recipes/v2?type=public&q=${query}&app_id=${API_ID}&app_key=${API_KEY}${healthOptions}`

Getting your API ID and API KEY is easy enough when you subscribe as a developer. It’s a bit trickier to capture a query and format it correctly. If you are searching for “tempeh, quinoa, red pepper”, your query variable needs to appear as

tempeh%20quinoa%20red%20pepper . The query is held in the component’s local state. This bit of code using Regex captures any combinations of spaces and commas and replaces them with “ %20 ”:

query = this.state.searchString.split(/\s+|,\s+/).join("%20")

For capturing multiple dietary requirements, I used React-Select, a very easy-to-use and versatile select component that makes handling multiple options a breeze. With every selection, a new array of objects that includes the label and value of each option is sent to your select handler. Then you simply overwrite your local state property with the array. React-Select sends an object with the onChange prop, not an event. Options such as “gluten-free, vegan” must be parsed as &health=gluten-free&health=vegan . A simple map through the array solves that problem easily:

healthOptions = this.state.healthOptions.map((option) => "&health=" + option.value).join("")

Then a call to fetchRecipes(query, healthOptions) sends the query to the Edamam API through the fetchRecipes action, which returns JSON objects in groups of 20 recipes. If a recipe is saved, selected properties are mapped to a Recipe object that is stored in the database through the Rails API backend. A “My Recipes” page allows the user to view recipes that have been previously saved, and can add to them through more searches, or delete the ones they no longer wish to keep.

Vegan and kosher lasagna recipes

Recipe Details with links to original website

My Recipes

Now that the basic mechanics of getting a selective, refined search has been accomplished, I can move ahead to building out more complex features to make this a versatile and useful app for anyone who like to experiment with new recipes and organize the ones they use time and again.

When I first started to learn React, I didn’t understand the point of all the prop passing and the component mindset. It wasn’t until I sat down to code up this app that I began to appreciate the modularity. I found that I ran into far fewer coding rabbit holes than with previous projects. I think the biggest challenge came from styling the app. At first, I didn’t understand that React compiles all your component .css files into one big file. This was problematic for me as I was using Bootstrap and had to change the .card class to include a hover effect in one display component. Imagine my chagrin to discover that ALL my cards were hovering throughout the app. The fix turned out to be to create a complex class in the pertinent .css file to the card component that I wanted to hover:

.card.myCard {

box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

transition: 0.3s;

}



.card.myCard:hover {

box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);

transform: scale(1.02);

}

Then you can use this special hover card anywhere in your React app using className=”card myCard" on the div.

When you want a standard card that doesn’t hover, simply use className=”card” .

The last trick I picked up was getting my queries to persist in the input and select boxes when migrating to and from the page. I learned about using localStorage, which is included in your browser.

When your form is submitted ( onSubmit ), include items from your local state using the following methods:

localStorage.setItem("query", this.state.searchString)

localStorage.setItem("healthOptions", JSON.stringify(this.state.healthOptions))

If you are saving an array, it’s necessary to stringify it first, because, well, browsers use strings to pass info. When you want to restore this information to the input and select boxes upon returning to the container component, put this in componentDidMount :

let query = localStorage.getItem("query")

let obj = localStorage.getItem("healthOptions")

and reset your local state with these variables. Don’t forget to JSON.parse() any arrays you might have stringified when storing. If you’ve remembered to use a value= prop in your input and select controls that ties into your local state, you are good to go.

Finally, to reset your controls to their default values or placeholders, throw in a reset button to clear both the local state and the localStorage:

handleReset = (e) => {

this.setState({

searchString: "",

healthOptions: []

})

localStorage.removeItem("query")

localStorage.removeItem("healthOptions")

}

Mahlzeit! was my first React/Redux project, so I kept my goals very modest. It’s a very different programming experience from using a vanilla JavaScript front-end, but once you start to understand the flow and communication between all the moving parts, you really can program a lot more confidently and debug more quickly. Our instruction at Flatiron centered around “old-style” React — using class components and local state objects in the constructor — important to know when dealing with legacy code. But React Hooks has simplified working with state and the Redux global store, and I will definitely go through the process of transferring my class components over to functional components to take full advantage of Hooks. I’m looking forward to converting my other Flatiron projects to use a React frontend to achieve the cleaner code and faster response React provides.