在本部落格中,我們將使用 React 建立一個食譜來尋找網站。該應用程式允許用戶搜尋他們最喜歡的食譜,查看趨勢或新食譜,並保存他們最喜歡的食譜。我們將利用 Edamam API 來取得即時食譜資料並將其動態顯示在網站上。
食譜查找器允許使用者:
src/ │ ├── components/ │ └── Navbar.js │ ├── pages/ │ ├── Home.js │ ├── About.js │ ├── Trending.js │ ├── NewRecipe.js │ ├── RecipeDetail.js │ ├── Contact.js │ └── Favorites.js │ ├── App.js ├── index.js ├── App.css └── index.css
要在本地運行此項目,請按照以下步驟操作:
git clone https://github.com/abhishekgurjar-in/recipe-finder.git cd recipe-finder
npm install
npm start
從 Edamam 網站取得您的 Edamam API 憑證(API ID 和 API 金鑰)。
在進行 API 呼叫的頁面中新增您的 API 憑證,例如 Home.js、Trending.js、NewRecipe.js 和 RecipeDetail.js。
import React from "react"; import Navbar from "./components/Navbar"; import { Route, Routes } from "react-router-dom"; import "./App.css"; import Home from "./pages/Home"; import About from "./pages/About"; import Trending from "./pages/Trending"; import NewRecipe from "./pages/NewRecipe"; import RecipeDetail from "./pages/RecipeDetail"; import Contact from "./pages/Contact"; import Favorites from "./pages/Favorites"; const App = () => { return ( <> <Navbar /> <Routes> <Route path="/" element={<Home />} /> <Route path="/trending" element={<Trending />} /> <Route path="/new-recipe" element={<NewRecipe />} /> <Route path="/new-recipe" element={<NewRecipe />} /> <Route path="/recipe/:id" element={<RecipeDetail />} /> <Route path="/about" element={<About />} /> <Route path="/contact" element={<Contact/>} /> <Route path="/favorites" element={<Favorites/>} /> </Routes> <div className="footer"> <p>Made with ❤️ by Abhishek Gurjar</p> </div> </> ); }; export default App;
這是使用者可以使用 Edamam API 搜尋食譜的主頁。
import React, { useState, useRef, useEffect } from "react"; import { IoSearch } from "react-icons/io5"; import { Link } from "react-router-dom"; const Home = () => { const [query, setQuery] = useState(""); const [recipe, setRecipe] = useState([]); const recipeSectionRef = useRef(null); const API_ID = "2cbb7807"; const API_KEY = "17222f5be3577d4980d6ee3bb57e9f00"; const getRecipe = async () => { if (!query) return; // Add a check to ensure the query is not empty const response = await fetch( `https://api.edamam.com/search?q=${query}&app_id=${API_ID}&app_key=${API_KEY}` ); const data = await response.json(); setRecipe(data.hits); console.log(data.hits); }; // Use useEffect to detect changes in the recipe state and scroll to the recipe section useEffect(() => { if (recipe.length > 0 && recipeSectionRef.current) { recipeSectionRef.current.scrollIntoView({ behavior: "smooth" }); } }, [recipe]); // Handle key down event to trigger getRecipe on Enter key press const handleKeyDown = (e) => { if (e.key === "Enter") { getRecipe(); } }; return ( <div className="home"> <div className="home-main"> <div className="home-text"> <h1>Find your Favourite Recipe</h1> </div> <div className="input-box"> <span> <input type="text" placeholder="Enter Recipe" onChange={(e) => setQuery(e.target.value)} onKeyDown={handleKeyDown} // Add the onKeyDown event handler /> </span> <IoSearch className="search-btn" onClick={getRecipe} /> </div> </div> <div ref={recipeSectionRef} className="recipes"> {recipe.map((item, index) => ( <div key={index} className="recipe"> <img className="recipe-img" src={item.recipe.image} alt={item.recipe.label} /> <h2 className="label">{item.recipe.label}</h2> <Link to={`/recipe/${item.recipe.uri.split("_")[1]}`}> <button className="button">View Recipe</button> </Link> </div> ))} </div> </div> ); }; export default Home;
此頁面取得並顯示趨勢食譜。
import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; const Trending = () => { const [trendingRecipes, setTrendingRecipes] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const API_ID = "2cbb7807"; const API_KEY = "17222f5be3577d4980d6ee3bb57e9f00"; useEffect(() => { const fetchTrendingRecipes = async () => { try { const response = await fetch( `https://api.edamam.com/api/recipes/v2?type=public&q=trending&app_id=${API_ID}&app_key=${API_KEY}` ); if (!response.ok) { throw new Error("Network response was not ok"); } const data = await response.json(); setTrendingRecipes(data.hits); setLoading(false); } catch (error) { setError("Failed to fetch trending recipes"); setLoading(false); } }; fetchTrendingRecipes(); }, []); if (loading) return ( <div className="loader-section"> <div className="loader"></div> </div> ); if (error) return <div>{error}</div>; return ( <div className="trending-recipe"> <div className="trending-recipe-main"> <div className="trending-recipe-text"> <h1>Trending Recipes</h1> </div> </div> <div className="recipes"> {trendingRecipes.map((item, index) => ( <div key={index} className="recipe"> <img className="recipe-img" src={item.recipe.image} alt={item.recipe.label} /> <h2 className="label">{item.recipe.label}</h2> <Link to={`/recipe/${item.recipe.uri.split("_")[1]}`}> <button className="button">View Recipe</button> </Link> </div> ))} </div> </div> ); }; export default Trending;
此頁面取得新食譜並顯示新食譜。
import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; const NewRecipe = () => { const [newRecipes, setNewRecipes] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const API_ID = "2cbb7807"; const API_KEY = "17222f5be3577d4980d6ee3bb57e9f00"; useEffect(() => { const fetchNewRecipes = async () => { try { const response = await fetch( `https://api.edamam.com/api/recipes/v2?type=public&q=new&app_id=${API_ID}&app_key=${API_KEY}` ); if (!response.ok) { throw new Error("Network response was not ok"); } const data = await response.json(); setNewRecipes(data.hits); setLoading(false); } catch (error) { setError("Failed to fetch new recipes"); setLoading(false); } }; fetchNewRecipes(); }, []); if (loading) return ( <div className="loader-section"> <div className="loader"></div> </div> ); if (error) return <div>{error}</div>; return ( <div className="new-recipe"> <div className="new-recipe-main"> <div className="new-recipe-text"> <h1>New Recipes</h1> </div> </div> <div className="recipes"> {newRecipes.map((item, index) => ( <div key={index} className="recipe"> <img className="recipe-img" src={item.recipe.image} alt={item.recipe.label} /> <h2 className="label">{item.recipe.label}</h2> <Link to={`/recipe/${item.recipe.uri.split("_")[1]}`}> <button className="button">View Recipe</button> </Link> </div> ))} </div> </div> ); }; export default NewRecipe;
此頁面取得並顯示主頁和搜尋的食譜。
import React, { useState, useRef, useEffect } from "react"; import { IoSearch } from "react-icons/io5"; import { Link } from "react-router-dom"; const Home = () => { const [query, setQuery] = useState(""); const [recipe, setRecipe] = useState([]); const recipeSectionRef = useRef(null); const API_ID = "2cbb7807"; const API_KEY = "17222f5be3577d4980d6ee3bb57e9f00"; const getRecipe = async () => { if (!query) return; // Add a check to ensure the query is not empty const response = await fetch( `https://api.edamam.com/search?q=${query}&app_id=${API_ID}&app_key=${API_KEY}` ); const data = await response.json(); setRecipe(data.hits); console.log(data.hits); }; // Use useEffect to detect changes in the recipe state and scroll to the recipe section useEffect(() => { if (recipe.length > 0 && recipeSectionRef.current) { recipeSectionRef.current.scrollIntoView({ behavior: "smooth" }); } }, [recipe]); // Handle key down event to trigger getRecipe on Enter key press const handleKeyDown = (e) => { if (e.key === "Enter") { getRecipe(); } }; return ( <div className="home"> <div className="home-main"> <div className="home-text"> <h1>Find your Favourite Recipe</h1> </div> <div className="input-box"> <span> <input type="text" placeholder="Enter Recipe" onChange={(e) => setQuery(e.target.value)} onKeyDown={handleKeyDown} // Add the onKeyDown event handler /> </span> <IoSearch className="search-btn" onClick={getRecipe} /> </div> </div> <div ref={recipeSectionRef} className="recipes"> {recipe.map((item, index) => ( <div key={index} className="recipe"> <img className="recipe-img" src={item.recipe.image} alt={item.recipe.label} /> <h2 className="label">{item.recipe.label}</h2> <Link to={`/recipe/${item.recipe.uri.split("_")[1]}`}> <button className="button">View Recipe</button> </Link> </div> ))} </div> </div> ); }; export default Home;
此頁面顯示最喜歡的食譜。
import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; const Favorites = () => { const [favorites, setFavorites] = useState([]); useEffect(() => { const savedFavorites = JSON.parse(localStorage.getItem("favorites")) || []; setFavorites(savedFavorites); }, []); if (favorites.length === 0) { return <div>No favorite recipes found.</div>; } return ( <div className="favorites-page "> <div className="favorite-recipes-text"> <h1>Favorite Recipes</h1> </div> <ul className="recipes"> {favorites.map((recipe) => ( <div className="recipe"> <img className="recipe-img" src={recipe.image} alt={recipe.label} /> <h2 className="label">{recipe.label}</h2> <Link to={`/recipe/${recipe.uri.split("_")[1]}`}> <button className="button">View Recipe</button> </Link> </div> ))} </ul> </div> ); }; export default Favorites;
此頁顯示食譜。
import React, { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; const RecipeDetail = () => { const { id } = useParams(); // Use React Router to get the recipe ID from the URL const [recipe, setRecipe] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [favorites, setFavorites] = useState([]); const API_ID = "2cbb7807"; const API_KEY = "17222f5be3577d4980d6ee3bb57e9f00"; useEffect(() => { const fetchRecipeDetail = async () => { try { const response = await fetch( `https://api.edamam.com/api/recipes/v2/${id}?type=public&app_id=${API_ID}&app_key=${API_KEY}` ); if (!response.ok) { throw new Error("Network response was not ok"); } const data = await response.json(); setRecipe(data.recipe); setLoading(false); } catch (error) { setError("Failed to fetch recipe details"); setLoading(false); } }; fetchRecipeDetail(); }, [id]); useEffect(() => { const savedFavorites = JSON.parse(localStorage.getItem("favorites")) || []; setFavorites(savedFavorites); }, []); const addToFavorites = () => { const updatedFavorites = [...favorites, recipe]; setFavorites(updatedFavorites); localStorage.setItem("favorites", JSON.stringify(updatedFavorites)); }; const removeFromFavorites = () => { const updatedFavorites = favorites.filter( (fav) => fav.uri !== recipe.uri ); setFavorites(updatedFavorites); localStorage.setItem("favorites", JSON.stringify(updatedFavorites)); }; const isFavorite = favorites.some((fav) => fav.uri === recipe?.uri); if (loading) return ( <div className="loader-section"> <div className="loader"></div> </div> ); if (error) return <div>{error}</div>; return ( <div className="recipe-detail"> {recipe && ( <> <div className="recipe-details-text" > <h1>{recipe.label}</h1> <h2>Ingredients:</h2> <ul> {recipe.ingredientLines.map((ingredient, index) => ( <li key={index}>{ingredient}</li> ))} </ul> <h2>Instructions:</h2> {/* Note: Edamam API doesn't provide instructions directly. You might need to link to the original recipe URL */} <p> For detailed instructions, please visit the{" "} <a href={recipe.url} target="_blank" rel="noopener noreferrer"> Recipe Instruction </a> </p> {isFavorite ? ( <button className="fav-btn" onClick={removeFromFavorites}>Remove from Favorites</button> ) : ( <button className="fav-btn" onClick={addToFavorites}>Add to Favorites</button> )} </div> <div className="recipe-details-img"> <img src={recipe.image} alt={recipe.label} /> </div> </> )} </div> ); }; export default RecipeDetail;
此頁面顯示聯絡頁面。
import React, { useState } from 'react'; const Contact = () => { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [message, setMessage] = useState(''); const [showPopup, setShowPopup] = useState(false); const handleSubmit = (e) => { e.preventDefault(); // Prepare the contact details object const contactDetails = { name, email, message }; // Save contact details to local storage const savedContacts = JSON.parse(localStorage.getItem('contacts')) || []; savedContacts.push(contactDetails); localStorage.setItem('contacts', JSON.stringify(savedContacts)); // Log the form data console.log('Form submitted:', contactDetails); // Clear form fields setName(''); setEmail(''); setMessage(''); // Show popup setShowPopup(true); }; const closePopup = () => { setShowPopup(false); }; return ( <div className="contact"> <h1>Contact Us</h1> <form onSubmit={handleSubmit} className="contact-form"> <div className="form-group"> <label htmlFor="name">Name:</label> <input type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} required /> </div> <div className="form-group"> <label htmlFor="email">Email:</label> <input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} required /> </div> <div className="form-group"> <label htmlFor="message">Message:</label> <textarea id="message" value={message} onChange={(e) => setMessage(e.target.value)} required ></textarea> </div> <button type="submit">Submit</button> </form> {showPopup && ( <div className="popup"> <div className="popup-inner"> <h2>Thank you!</h2> <p>Your message has been submitted successfully.</p> <button onClick={closePopup}>Close</button> </div> </div> )} </div> ); }; export default Contact;
此頁顯示關於頁面。
import React from 'react'; const About = () => { return ( <div className="about"> <div className="about-main"> <h1>About Us</h1> <p> Welcome to Recipe Finder, your go-to place for discovering delicious recipes from around the world! </p> <p> Our platform allows you to search for recipes based on your ingredients or dietary preferences. Whether you're looking for a quick meal, a healthy option, or a dish to impress your friends, we have something for everyone. </p> <p> We use the Edamam API to provide you with a vast database of recipes. You can easily find new recipes, view detailed instructions, and explore new culinary ideas. </p> <p> <strong>Features:</strong> <ul> <li>Search for recipes by ingredient, cuisine, or dietary restriction.</li> <li>Browse new and trending recipes.</li> <li>View detailed recipe instructions and ingredient lists.</li> <li>Save your favorite recipes for quick access.</li> </ul> </p> <p> Our mission is to make cooking enjoyable and accessible. We believe that everyone should have the tools to cook great meals at home. </p> </div> </div> ); }; export default About;
您可以在這裡查看該專案的現場演示。
食譜查找網站對於任何想要發現新的和流行食譜的人來說是一個強大的工具。透過利用 React 作為前端和 Edamam API 來處理數據,我們可以提供無縫的使用者體驗。您可以透過新增分頁、使用者驗證甚至更詳細的篩選選項等功能來進一步自訂此項目。
隨意嘗試該項目並使其成為您自己的!
Abhishek Gurjar 是一位專注的 Web 開發人員,熱衷於創建實用且功能性的 Web 應用程式。在 GitHub 上查看他的更多專案。
以上是使用 React 建立食譜查找器網站的詳細內容。更多資訊請關注PHP中文網其他相關文章!