0

0

使用 React 构建食谱查找器网站

DDD

DDD

发布时间:2024-09-13 13:58:11

|

925人浏览过

|

来源于dev.to

转载

使用 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

安装

要在本地运行此项目,请按照以下步骤操作:

  1. 克隆存储库:
   git clone https://github.com/abhishekgurjar-in/recipe-finder.git
   cd recipe-finder
  1. 安装依赖项:
   npm install
  1. 启动 react 应用程序:
   npm start
  1. 从 edamam 网站获取您的 edamam api 凭证(api id 和 api 密钥)。

  2. 在进行 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">
            @@##@@
            <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">
            @@##@@
            <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">
            @@##@@
            <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

此页面获取并显示主页和搜索的食谱。

Unscreen
Unscreen

AI智能视频背景移除工具

下载
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">
            @@##@@
            <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">
            @@##@@
            <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">
        @@##@@
        </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 上查看他的更多项目。

{item.recipe.label}{item.recipe.label}{item.recipe.label}{item.recipe.label}{recipe.label}{recipe.label}

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

530

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

576

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js是什么意思
js是什么意思

JS是JavaScript的缩写,它是一种广泛应用于网页开发的脚本语言。JavaScript是一种解释性的、基于对象和事件驱动的编程语言,通常用于为网页增加交互性和动态性。它可以在网页上实现复杂的功能和效果,如表单验证、页面元素操作、动画效果、数据交互等。

6202

2023.08.17

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

492

2023.09.01

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

Js中concat和push的区别
Js中concat和push的区别

Js中concat和push的区别:1、concat用于将两个或多个数组合并成一个新数组,并返回这个新数组,而push用于向数组的末尾添加一个或多个元素,并返回修改后的数组的新长度;2、concat不会修改原始数组,是创建新的数组,而push会修改原数组,将新元素添加到原数组的末尾等等。本专题为大家提供concat和push相关的文章、下载、课程内容,供大家免费下载体验。

240

2023.09.14

js截取字符串的方法介绍
js截取字符串的方法介绍

JavaScript字符串截取方法,包括substring、slice、substr、charAt和split方法。这些方法可以根据具体需求,灵活地截取字符串的不同部分。在实际开发中,根据具体情况选择合适的方法进行字符串截取,能够提高代码的效率和可读性 。

303

2023.09.21

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.9万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

CSS教程
CSS教程

共754课时 | 42.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号