”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 React 构建食谱查找器网站

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

发布于2024-11-07
浏览:991

Building Recipe Finder Website using React

Introduction

In this blog, we'll be building a Recipe Finder Website using React. This app allows users to search for their favorite recipes, view trending or new recipes, and save their favorite ones. We will leverage the Edamam API to fetch real-time recipe data and display it dynamically on the website.

Project Overview

The Recipe Finder allows users to:

  • Search for recipes by name.
  • View trending and newly added recipes.
  • View detailed information about individual recipes.
  • Add recipes to a favorites list and persist the data using localStorage.

Features

  • Search Functionality: Users can search for recipes by entering a query.
  • Trending Recipes: Displays currently trending recipes from the API.
  • New Recipes: Displays the latest recipes from the API.
  • Recipe Detail: Displays detailed information about a selected recipe.
  • Favorites: Allows users to add recipes to a favorites list, which is saved locally.

Technologies Used

  • React: For building the user interface.
  • React Router: For navigation between different pages.
  • Edamam API: For fetching recipes.
  • CSS: For styling the application.

Project Structure

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

Installation

To run this project locally, follow these steps:

  1. Clone the repository:
   git clone https://github.com/abhishekgurjar-in/recipe-finder.git
   cd recipe-finder
  1. Install the dependencies:
   npm install
  1. Start the React app:
   npm start
  1. Obtain your Edamam API credentials (API ID and API Key) from the Edamam website.

  2. Add your API credentials inside the pages where API calls are made, such as Home.js, Trending.js, NewRecipe.js, and RecipeDetail.js.

Usage

App.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 (
    
      
      
        } />
        } />
        } />
        } />
        } />
        } />
        } />
        } />
      
   

Made with ❤️ by Abhishek Gurjar

> ); }; export default App;
Home.js

This is the main page where users can search for recipes using the 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 (
    

Find your Favourite Recipe

setQuery(e.target.value)} onKeyDown={handleKeyDown} // Add the onKeyDown event handler />
{recipe.map((item, index) => (
{item.recipe.label}

{item.recipe.label}

))}
); }; export default Home;
Trending.js

This page fetches and displays trending recipes.

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 (
      
); if (error) return
{error}
; return (

Trending Recipes

{trendingRecipes.map((item, index) => (
{item.recipe.label}

{item.recipe.label}

))}
); }; export default Trending;
NewRecipe.js

This page fetches NewRecipe and displays New Recipes.

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 (
      
); if (error) return
{error}
; return (

New Recipes

{newRecipes.map((item, index) => (
{item.recipe.label}

{item.recipe.label}

))}
); }; export default NewRecipe;
Home.js

This page fetches and displays Home page and serached recipe.

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 (
    

Find your Favourite Recipe

setQuery(e.target.value)} onKeyDown={handleKeyDown} // Add the onKeyDown event handler />
{recipe.map((item, index) => (
{item.recipe.label}

{item.recipe.label}

))}
); }; export default Home;
Favorites.js

This page displays Favorites recipes.

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 
No favorite recipes found.
; } return (

Favorite Recipes

    {favorites.map((recipe) => (
    {recipe.label}

    {recipe.label}

    ))}
); }; export default Favorites;
RecipeDetail.js

This page displays of the recipes.

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 (
      
); if (error) return
{error}
; return (
{recipe && (

{recipe.label}

Ingredients:

    {recipe.ingredientLines.map((ingredient, index) => (
  • {ingredient}
  • ))}

Instructions:

{/* Note: Edamam API doesn't provide instructions directly. You might need to link to the original recipe URL */}

For detailed instructions, please visit the{" "} Recipe Instruction

{isFavorite ? ( ) : ( )}
{recipe.label}
> )}
); }; export default RecipeDetail;
Contact.js

This page displays Contact page.

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 (
    

Contact Us

setName(e.target.value)} required />
setEmail(e.target.value)} required />
{showPopup && (

Thank you!

Your message has been submitted successfully.

)}
); }; export default Contact;
About.js

This page displays About Page.

import React from 'react';

const About = () => {
  return (
    

About Us

Welcome to Recipe Finder, your go-to place for discovering delicious recipes from around the world!

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.

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.

Features:

  • Search for recipes by ingredient, cuisine, or dietary restriction.
  • Browse new and trending recipes.
  • View detailed recipe instructions and ingredient lists.
  • Save your favorite recipes for quick access.

Our mission is to make cooking enjoyable and accessible. We believe that everyone should have the tools to cook great meals at home.

); }; export default About;

Live Demo

You can view the live demo of the project here.

Conclusion

The Recipe Finder Website is a powerful tool for anyone looking to discover new and trending recipes. By leveraging React for the front end and the Edamam API for data, we can provide a seamless user experience. You can further customize this project by adding features such as pagination, user authentication, or even more detailed filtering options.

Feel free to experiment with the project and make it your own!

Credits

  • API: Edamam
  • Icons: React Icons

Author

Abhishek Gurjar is a dedicated web developer passionate about creating practical and functional web applications. Check out more of his projects on GitHub.

版本声明 本文转载于:https://dev.to/abhishekgurjar/building-recipe-finder-website-using-react-12j7?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何配置Pytesseract以使用数字输出的单位数字识别?
    如何配置Pytesseract以使用数字输出的单位数字识别?
    Pytesseract OCR具有单位数字识别和仅数字约束 在pytesseract的上下文中,在配置tesseract以识别单位数字和限制单个数字和限制输出对数字可能会提出质疑。 To address this issue, we delve into the specifics of Te...
    编程 发布于2025-04-16
  • 如何在JavaScript对象中动态设置键?
    如何在JavaScript对象中动态设置键?
    在尝试为JavaScript对象创建动态键时,如何使用此Syntax jsObj['key' i] = 'example' 1;不工作。正确的方法采用方括号: jsobj ['key''i] ='example'1; 在JavaScript中,数组是一...
    编程 发布于2025-04-16
  • 如何将多种用户类型(学生,老师和管理员)重定向到Firebase应用中的各自活动?
    如何将多种用户类型(学生,老师和管理员)重定向到Firebase应用中的各自活动?
    Red: How to Redirect Multiple User Types to Respective ActivitiesUnderstanding the ProblemIn a Firebase-based voting app with three distinct user type...
    编程 发布于2025-04-16
  • 如何避免Go语言切片时的内存泄漏?
    如何避免Go语言切片时的内存泄漏?
    ,a [j:] ...虽然通常有效,但如果使用指针,可能会导致内存泄漏。这是因为原始的备份阵列保持完整,这意味着新切片外部指针引用的任何对象仍然可能占据内存。 copy(a [i:] 对于k,n:= len(a)-j i,len(a); k
    编程 发布于2025-04-16
  • 如何克服PHP的功能重新定义限制?
    如何克服PHP的功能重新定义限制?
    克服PHP的函数重新定义限制在PHP中,多次定义一个相同名称的函数是一个no-no。尝试这样做,如提供的代码段所示,将导致可怕的“不能重新列出”错误。 但是,PHP工具腰带中有一个隐藏的宝石:runkit扩展。它使您能够灵活地重新定义函数。 runkit_function_renction_re...
    编程 发布于2025-04-16
  • 如何将来自三个MySQL表的数据组合到新表中?
    如何将来自三个MySQL表的数据组合到新表中?
    mysql:从三个表和列的新表创建新表 答案:为了实现这一目标,您可以利用一个3-way Join。 选择p。*,d.content作为年龄 来自人为p的人 加入d.person_id = p.id上的d的详细信息 加入T.Id = d.detail_id的分类法 其中t.taxonomy =...
    编程 发布于2025-04-16
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-04-16
  • `console.log`显示修改后对象值异常的原因
    `console.log`显示修改后对象值异常的原因
    foo = [{id:1},{id:2},{id:3},{id:4},{id:id:5},],]; console.log('foo1',foo,foo.length); foo.splice(2,1); console.log('foo2', foo, foo....
    编程 发布于2025-04-16
  • 对象拟合:IE和Edge中的封面失败,如何修复?
    对象拟合:IE和Edge中的封面失败,如何修复?
    To resolve this issue, we employ a clever CSS solution that solves the problem:position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%)...
    编程 发布于2025-04-16
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在Java中的多个返回类型:一种误解类型:在Java编程中揭示,在Java编程中,Peculiar方法签名可能会出现,可能会出现,使开发人员陷入困境,使开发人员陷入困境。 getResult(string s); ,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但这确实是如此吗...
    编程 发布于2025-04-16
  • 为什么我在Silverlight Linq查询中获得“无法找到查询模式的实现”错误?
    为什么我在Silverlight Linq查询中获得“无法找到查询模式的实现”错误?
    查询模式实现缺失:解决“无法找到”错误在Silverlight应用程序中,尝试使用LINQ建立LINQ连接以错误而实现的数据库”,无法找到查询模式的实现。”当省略LINQ名称空间或查询类型缺少IEnumerable 实现时,通常会发生此错误。 解决问题来验证该类型的质量是至关重要的。在此特定实例中...
    编程 发布于2025-04-16
  • 如何在其容器中为DIV创建平滑的左右CSS动画?
    如何在其容器中为DIV创建平滑的左右CSS动画?
    通用CSS动画,用于左右运动 ,我们将探索创建一个通用的CSS动画,以向左和右移动DIV,从而到达其容器的边缘。该动画可以应用于具有绝对定位的任何div,无论其未知长度如何。问题:使用左直接导致瞬时消失 更加流畅的解决方案:混合转换和左 [并实现平稳的,线性的运动,我们介绍了线性的转换。这...
    编程 发布于2025-04-16
  • 我可以将加密从McRypt迁移到OpenSSL,并使用OpenSSL迁移MCRYPT加密数据?
    我可以将加密从McRypt迁移到OpenSSL,并使用OpenSSL迁移MCRYPT加密数据?
    将我的加密库从mcrypt升级到openssl 问题:是否可以将我的加密库从McRypt升级到OpenSSL?如果是这样,如何?答案:是的,可以将您的Encryption库从McRypt升级到OpenSSL。可以使用openssl。附加说明: [openssl_decrypt()函数要求iv参...
    编程 发布于2025-04-16
  • 解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    Resolving Incorrect String Value Exception When Inserting EmojiWhen attempting to insert a string containing emoji characters into a MySQL database us...
    编程 发布于2025-04-16
  • Python元类工作原理及类创建与定制
    Python元类工作原理及类创建与定制
    python中的metaclasses是什么? Metaclasses负责在Python中创建类对象。就像类创建实例一样,元类也创建类。他们提供了对类创建过程的控制层,允许自定义类行为和属性。在Python中理解类作为对象的概念,类是描述用于创建新实例或对象的蓝图的对象。这意味着类本身是使用类关...
    编程 发布于2025-04-16

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3