このブログでは、React を使用して レシピ ファインダー Web サイト を構築します。このアプリを使用すると、ユーザーはお気に入りのレシピを検索したり、トレンドや新しいレシピを表示したり、お気に入りのレシピを保存したりできます。 Edamam API を利用してリアルタイムのレシピ データを取得し、Web サイトに動的に表示します。
レシピ ファインダーを使用すると、ユーザーは次のことができます:
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 Web サイトから 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 は、実用的で機能的な Web アプリケーションの作成に情熱を注ぐ専任の Web 開発者です。 GitHub で彼のプロジェクトをさらにチェックしてください。
以上がReact を使用してレシピ ファインダー Web サイトを構築するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。