Juneikerc.com

How to create a random meal generator with qwik.

featured image of post: How to create a random meal generator with qwik.

While searching for exercises to do with qwik, I came across a random meal generator, and I believe it's a good way to explain some of qwik's features like routeAction$, routing system, etc.

In this tutorial, you will learn how to create a random meal generator using qwik. Before you begin, you should know that to follow this tutorial, you need to be familiar with at least the basic fundamentals of how qwik works.

Project Explanation:

As a foundation for this project, I need to create at least two routes:

  1. The home page: with the button to generate the meal.
  2. The recipe page: this page should be dynamic and contain all the information about the meal.

I will use the themealdb API to obtain all the necessary information.

Important Note

You can add more features such as obtaining meals by category or area, but for the purposes of this tutorial, I will focus only on the basics.

Creating the home page and a routeAction$

tsx
import { component$ } from "@builder.io/qwik";
import { routeAction$, type DocumentHead } from "@builder.io/qwik-city";
export const useRandomMealAction = routeAction$(async (_, { redirect }) => {
const response = await fetch(
"https://www.themealdb.com/api/json/v1/1/random.php"
);
const data = await response.json();
const randomMeal = data.meals[0];
const mealRoute = randomMeal.strMeal
.replace(/[^\w\s]/g, "")
.replaceAll(" ", "-")
.toLowerCase();
redirect(302, `/${mealRoute}`);
});
export default component$(() => {
const action = useRandomMealAction();
return (
<main>
<section>
<h1>Click on button to generate random meal</h1>
<h2>You don't know what to eat?</h2>
<p>Use our Random meal Generator</p>
<button
onClick$={async () => {
await action.submit();
}}
>
Generate meal
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-wand"
width="20"
height="20"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="#2c3e50"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M6 21l15 -15l-3 -3l-15 15l3 3" />
<path d="M15 6l3 3" />
<path d="M9 3a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2" />
<path d="M19 13a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2" />
</svg>
</button>
</section>
</main>
);
});
export const head: DocumentHead = {
title: "Welcome to Qwik",
meta: [
{
name: "description",
content: "Qwik site description",
},
],
};

The routeAction$ actions are used to handle form submissions, allowing for side effects such as writing to a database or sending an email.

In this case, we need that when clicking on a button, an action is triggered to call an API that will return a random recipe. Once the recipe is obtained, redirect to a dynamic URL that will contain all the data of the meal.

Before redirecting, we format the name of the recipe so that it can be a valid URL.

js
const mealRoute = randomMeal.strMeal
.replace(/[^\w\s]/g, "")
.replaceAll(" ", "-")
.toLowerCase();

Obtaining meal data with routeLoader$

The recommended way to obtain data for the initial rendering in qwik is through the routeLoader$ method.

Qwik routeLoader to obtain recipe data

tsx
import { component$ } from "@builder.io/qwik";
import { routeLoader$ } from "@builder.io/qwik-city";
export const useGetMealData = routeLoader$(async ({ params }) => {
const response = await fetch(
`https://www.themealdb.com/api/json/v1/1/search.php?s=${params.meal.replaceAll(
"-",
" "
)}`
);
const data = await response.json();
const meal = data.meals[0];
const extractIngredients = extractElementsOfObject(
data.meals[0],
"strIngredient"
);
const extractMeasures = extractElementsOfObject(data.meals[0], "strMeasure");
const ingredients = extractIngredients.map((ingredient, index) => ({
ingredient,
measure: extractMeasures[index],
}));
return {
name: meal.strMeal,
category: meal.strCategory,
area: meal.strArea,
ingredients,
instructions: meal.strInstructions,
thumbnail: meal.strMealThumb,
tags: meal.strTags,
youtube: meal.strYoutube,
source: meal.strSource,
};
});

Auxiliary functions to format the API response.

ts
export const extractElementsOfObject = (object: any, propertyStr: string) => {
const elements = [];
for (const property in object) {
if (property.includes(propertyStr)) {
elements.push(object[property]);
}
}
return elements.filter((element) => element.trim() !== "");
};

Rendering the component to display the data of the randomly generated meal.

tsx
export default component$(() => {
const mealData = useGetMealData().value;
const youtubeVideoId = new URL(mealData.youtube).searchParams.get("v");
return (
<main>
<GoHome />
<section style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<div>
<img
src={mealData.thumbnail}
alt={mealData.name}
width={300}
height={300}
/>
<h2>Ingredients</h2>
<ul>
{mealData.ingredients.map((ingredient, i) => (
<li key={i}>
{ingredient.ingredient} - {ingredient.measure}
</li>
))}
</ul>
</div>
<div>
<h1>{mealData.name}</h1>
<p>{mealData.instructions}</p>
</div>
</section>
<h2>Video Recipe</h2>
<iframe
width={560}
height={315}
src={`https://www.youtube.com/embed/${youtubeVideoId}`}
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
></iframe>
</main>
);
});

I hope this tutorial has helped you understand a bit better how qwik works, especially its functions routeLoader$ and routeAction$. Thank you very much for reading.

Juneiker Castillo freelance web developer

I am Juneiker Castillo, a passionate front-end web developer deeply in love with programming and creating fast, scalable, and modern websites—a JavaScript enthusiast and a React.js lover ⚛️.

About me