在本博客中,我们将使用 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中文网其他相关文章!