이 블로그에서는 React를 사용하여 Recipe Finder 웹사이트를 구축해 보겠습니다. 이 앱을 통해 사용자는 자신이 좋아하는 레시피를 검색하고, 인기 레시피나 새로운 레시피를 확인하고, 즐겨찾는 레시피를 저장할 수 있습니다. 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 키)을 받으세요.
Home.js, Trending.js, NewRecipe.js, RecipeDetail.js 등 API 호출이 이루어지는 페이지 내에 API 자격 증명을 추가하세요.
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;
이 페이지는 NewRecipe를 가져오고 새로운 레시피를 표시합니다.
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는 실용적이고 기능적인 웹 애플리케이션 제작에 열정을 쏟는 헌신적인 웹 개발자입니다. GitHub에서 더 많은 프로젝트를 확인해 보세요.
위 내용은 React를 사용하여 Recipe Finder 웹 사이트 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!