Building the Main User-Interface with Functionality
Building the Main User-Interface with Functionality
This file builds the main functionality of your app. It handles user input, fetches recipes from the API based on the search, and displays them in a grid. It also manages states such as loading, no results, and errors, and allows users to click a recipe to view its details in a modal.
File Path: Recipe-Finder\src\components\RecipeFinder.jsx
import React from "react";
import { useState } from "react";
import RecipeModal from "./RecipeModal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSistrix } from "@fortawesome/free-brands-svg-icons";
import { faXmark, faBug } from "@fortawesome/free-solid-svg-icons";
function RecipeFinder() {
const [search, setSearch] = useState("");
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(false);
const [hasSearched, setHasSearched] = useState(false);
const [selectedRecipe, setSelectedRecipe] = useState(null);
const fetchRecipes = async () => {
if (!search) return;
setHasSearched(true);
setLoading(true);
try {
const response = await fetch(
`https://tasty.p.rapidapi.com/recipes/list?from=0&size=10&q=${search}`,
{
method: "GET",
headers: {
"X-RapidAPI-Key":
"7a3a553868mshd9dc2d1e96281a6p1ce958jsn48611636ad48",
"X-RapidAPI-Host": "tasty.p.rapidapi.com",
},
},
);
const data = await response.json();
console.log("API Response:", data);
setRecipes(data.results || []);
} catch (error) {
console.error("Error fetching recipes:", error);
} finally {
setLoading(false);
}
};
return (
<>
<div className="recipe-finder-component w-[90%] md:w-[70%] lg:w-[80%] xl:w-[80%] py-5 px-2 mb-5">
<div className="search-bar-container w-full md:w-[90%] lg:w-[80%] xl:w-[50%] relative flex items-center">
<input
type="search"
name="search-recipe"
id="search-recipe"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
fetchRecipes();
}
}}
className="w-full text-base md:text-lg py-2 px-3"
placeholder="Search the recipe....."
/>
<FontAwesomeIcon
icon={faSistrix}
className="search-icon text-lg md:text-2xl absolute right-10 cursor-pointer"
onClick={fetchRecipes}
/>
{search && (
<FontAwesomeIcon
icon={faXmark}
className="clear-icon text-base md:text-lg absolute right-2 cursor-pointer"
onClick={() => {
setSearch("");
}}
/>
)}
</div>
<div className="recipe-container mt-6">
{loading ? (
<>
<div className="flex justify-center items-center">
<div className="loader"></div>
</div>
<p className="text-xl text-gray-600 mt-3">
Fetching delicious recipes.....
</p>
</>
) : !hasSearched ? (
<p className="text-gray-500 text-center text-base sm:text-lg">
Your recipes will be displayed here
</p>
) : recipes.length === 0 ? (
// Error Message Box
<div className="error-msg-container flex flex-col items-center justify-center py-6 px-4 md:px-6 lg:px-10">
<FontAwesomeIcon
icon={faBug}
style={{ color: "rgb(255, 60, 44)" }}
className="text-2xl mb-2"
/>
<p className="text-red-600 text-center text-sm md:text-base">
Recipe not found
</p>
<p className="text-red-600 text-center text-sm md:text-base mt-1">
Please check the name or try searching again.
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 recipe-grid gap-2">
{recipes.map((recipe) => (
<div
key={recipe.id}
className="dish flex flex-col cursor-pointer"
onClick={() => {
setSelectedRecipe(recipe);
}}
>
<div className="img-container">
<img src={recipe.thumbnail_url} alt={recipe.name} />
</div>
<div className="dish-text p-3">
<p className="name text-lg line-clamp-1">{recipe.name}</p>
<p className="text-base mt-2 sm:mt-3">
<span className="prep -ml-0.75">⏱ Prep Time:</span>{" "}
{recipe.total_time_minutes
? recipe.total_time_minutes < 60
? `${recipe.total_time_minutes} mins`
: `${(recipe.total_time_minutes / 60).toFixed(1)} hrs`
: "N/A"}
</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
{/* Modal Dialog Component */}
<RecipeModal
recipe={selectedRecipe}
onClose={() => setSelectedRecipe(null)}
/>
</>
);
}
export default RecipeFinder;









