Converting Vanilla JavaScript to React
Making the MixIt app into MixItRemix, a React app, without using Router (Part 3: Rendering the selection to the body of the page)
We saw how I opted to get the MixItRemix menu and input forms to render and plugged in their functionality in the first two blog posts. This post will round out the version of the app conversion that doesn’t utilize the React Router hook.
I went back to the component hierarchy I built to get the rendering portions of the app working. This is where I learned some of the muscle React brings to bear. While the styling was still a persistent issue, rendering is really React’s strength and where it seems to be vastly superior to vanilla JavaScript. Not only rendering but unmounting renders to render other components or pages.
The container is really two parts. When one of the components that render a list is selected another element is created to house that list in the vanilla JS. Then the user selects an item from that list to render the details for that drink. The exception is the random cocktail option, which immediately renders a single random drink. For this reason in both the vanilla version and in the React version, I opted to separate the two components. In React this meant calling back a drink selection to App
and then passing the state down as a prop.
In the vanilla JS, a lot of lines are given to either using createElement
, getElementById
, or querySelector
and then painstakingly adding or altering attributes. React makes this experience much smoother by allowing us to simply put whatever element attributes we need right into our JSX.
function renderList(data) {drinksListContainer.innerHTML = '';
if(!data.drinks) {const p = document.createElement('p');p.textContent = 'No results found';p.className = 'no-results-p';drinksListContainer.append(p);return;}fetchRequest(byName, data.drinks[0].strDrink).then(drink => renderDrinkCard(drink.drinks[0]));data.drinks.forEach(drink => {const drinkListItem = document.createElement('li');const drinkName = document.createElement('h3');const drinkThumb = document.createElement('img');drinkThumb.className = "drink-thumb";drinkListItem.className = "drink-list-item";
drinkThumb.src = drink.strDrinkThumb;drinkName.textContent = drink.strDrink;drinkListItem.append(drinkThumb);drinkListItem.append(drinkName);drinksListContainer.append(drinkListItem);drinkListItem.addEventListener('click', () => {fetchRequest(byName, drink.strDrink).then(drink => renderDrinkCard(drink.drinks[0]));})})}
We can see a lot of real estate given to the combined creation and appending of elements to the DOM and even if the code has roughly the same number of lines, JSX is easier to write and much easier to understand at first glance.
const DrinksList = ({onDrinkSelection, drinks}) => {...
const drinksList = drinks.map(drink => {return (<Drinkkey={drink.IdDrink}drink={drink}handleDrinkClick={handleDrinkClick}/>)})return (<>{drinks.length ? <div id="drink-list" style={{display: 'block'}}>{drinksList}</div> : null}</>)}export default DrinksList;
… and the Drink
component:
const Drink = ({drink, handleDrinkClick}) => {return (<likey={drink.idDrink}onClick={handleDrinkClick}data-value={drink.strDrink}className="drink-list-item"><img className="drink-thumb" data-value={drink.strDrink} src={drink.strDrinkThumb} alt= {drink.strDrink}></img><h3 data-value={drink.strDrink}>{drink.strDrink}</h3></li>)}export default Drink
For the DrinkDetails
component it is more of the same. The logic for rendering the ingredients is the same and that is mostly a backend issue synchronizing measures with ingredients, but without needing such a complicated solution it will be easy to see how superior React is.
function renderDrinkCard(drink) {detailContainer.innerHTML = '';const detailName = document.createElement('h3');const detailImage = document.createElement('img');const ingredientsTitle = document.createElement('h4');const detailIngredients = document.createElement('ul');const instructionsTitle = document.createElement('h4');const detailInstructions = document.createElement('p');const glassTitle = document.createElement('h4');const glassType = document.createElement('p');detailImage.className = "detail-image";detailName.textContent = drink.strDrink;detailImage.src = drink.strDrinkThumb;instructionsTitle.textContent = 'Instructions: ';ingredientsTitle.textContent = 'Ingredients:';detailInstructions.textContent = drink.strInstructions;glassTitle.textContent = 'Glass: ';glassType.textContent = drink.strGlass;const drinkIngredients = [];let ingredientString;for (let i = 1; i < 16 ; i++) {if(drink[`strIngredient${i}`] === null) {break;} else if (drink[`strMeasure${i}`] === null) {ingredientString = drink[`strIngredient${i}`];} else {ingredientString = drink[`strMeasure${i}`] + ' ' + drink[`strIngredient${i}`];}drinkIngredients.push(ingredientString);}drinkIngredients.forEach(ingredient => {const detailIngredient = document.createElement('li')detailIngredient.textContent = ingredientdetailIngredients.append(detailIngredient)})detailContainer.append(detailName, detailImage, glassTitle, glassType,ingredientsTitle, detailIngredients, instructionsTitle, detailInstructions)}
… as compared to the MixItRemix equivalent
const DrinkDetails = ({drinkSelection}) => {const renderDrinkIngredients = () => {const drinkIngredients = [];let ingredientString;for (let i = 1; i < 16 ; i++) {if(drinkSelection[`strIngredient${i}`] === null) {break;} else if (drinkSelection[`strMeasure${i}`] === null) {ingredientString = drinkSelection[`strIngredient${i}`];} else {ingredientString = drinkSelection[`strMeasure${i}`] + ' ' + drinkSelection[`strIngredient${i}`];}drinkIngredients.push(ingredientString);}drinkIngredients.map(ingredient => {return <li key={ingredient}>{ingredient}</li>})}return (<div id="drink-details"><h3>{drinkSelection.strDrink}</h3><imgclassName="detail-image"src={drinkSelection.strDrinkThumb}alt={drinkSelection.strDrink}/><h4>Glass:</h4><p>{drinkSelection.strGlass}</p><h4>Ingredients:</h4><ul>{renderDrinkIngredients()}</ul><h4>Instructions:</h4><p>{drinkSelection.strInstructions}</p></div>)}export default DrinkDetails;
So again, the major takeaway is that rendering in React is much simpler and breaking the responsibilities of different sections of the app into components is much easier for another engineer to follow.
There are a few persistent bugs but I opted to publish as is because I intend to work through the MixItRemix app and include React Router and that itself will make for another interesting project and post. I believe that building a React app from scratch including the Router would have been much better and simpler than trying to retrofit that hook into the work but it will be great practice manipulating existing code.
As I hadn’t learned the Router functionality when I started this project, I am happy with the results. Some of the code is not as DRY as it could be and a strong refactor wouldn’t hurt the code at all. Still, I learned a lot about building an app in React that I can use in other projects or conversions from vanilla JavaScript. Working in parallel with an app written in vanilla JS came with some advantages but it also caused some major headaches. Synchronizing changes in CSS across layers of components was the greatest struggle. The greatest success was applying the API data to the rendering components, which was more straightforward.
We will be moving on to backend languages next phase but I’d like the add client side routing to the remix, so keep an eye out for another post on that subject in the future.