使用 React 构建食谱查找器网站
来源:dev.to
时间:2024-09-13 13:15:57 377浏览 收藏
“纵有疾风来,人生不言弃”,这句话送给正在学习文章的朋友们,也希望在阅读本文《使用 React 构建食谱查找器网站》后,能够真的帮助到大家。我也会在后续的文章中,陆续更新文章相关的技术文章,有好的建议欢迎大家在评论留言,非常感谢!
介绍
在本博客中,我们将使用 react 构建一个食谱查找网站。该应用程序允许用户搜索他们最喜欢的食谱,查看趋势或新食谱,并保存他们最喜欢的食谱。我们将利用 edamam api 获取实时食谱数据并将其动态显示在网站上。
项目概况
食谱查找器允许用户:
- 按名称搜索食谱。
- 查看趋势和新添加的食谱。
- 查看各个食谱的详细信息。
- 将食谱添加到收藏夹列表并使用 localstorage 保存数据。
特征
- 搜索功能:用户可以通过输入查询来搜索食谱。
- 热门食谱:显示来自 api 的当前热门食谱。
- 新菜谱:显示来自 api 的最新菜谱。
- 食谱详细信息:显示有关所选食谱的详细信息。
- 收藏夹:允许用户将食谱添加到收藏夹列表,该列表保存在本地。
使用的技术
- react:用于构建用户界面。
- react router:用于不同页面之间的导航。
- edamam api:用于获取食谱。
- css:用于设计应用程序的样式。
项目结构
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
- 启动 react 应用程序:
npm start
从 edamam 网站获取您的 edamam api 凭证(api id 和 api 密钥)。
在进行 api 调用的页面中添加您的 api 凭据,例如 home.js、trending.js、newrecipe.js 和 recipedetail.js。
用法
应用程序.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;
主页.js
这是用户可以使用 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;
trending.js
此页面获取并显示趋势食谱。
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;
新菜谱.js
此页面获取新食谱并显示新食谱。
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;
主页.js
此页面获取并显示主页和搜索的食谱。
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;
收藏夹.js
此页面显示最喜欢的食谱。
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;
recipedetail.js
此页面显示食谱。
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;
联系方式.js
此页面显示联系页面。
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;
关于.js
此页面显示关于页面。
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 来处理数据,我们可以提供无缝的用户体验。您可以通过添加分页、用户身份验证甚至更详细的过滤选项等功能来进一步自定义此项目。
随意尝试该项目并使其成为您自己的!
制作人员
- api:毛豆
- 图标:react 图标
作者
abhishek gurjar 是一位专注的 web 开发人员,热衷于创建实用且功能性的 web 应用程序。在 github 上查看他的更多项目。
终于介绍完啦!小伙伴们,这篇关于《使用 React 构建食谱查找器网站》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
109 收藏
-
446 收藏
-
301 收藏
-
299 收藏
-
325 收藏
-
348 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习