」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 使用 React 建立食譜查找器網站

使用 React 建立食譜查找器網站

發佈於2024-11-07
瀏覽:485

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]刪除
最新教學 更多>
  • 進階 T:依賴參數、推斷聯合以及 Twitter 上的健康互動。
    進階 T:依賴參數、推斷聯合以及 Twitter 上的健康互動。
    每次我用 TypeScript 写成 Foo 时,我都会感受到失败的沉重。 在一种情况下,这种感觉特别强烈:当函数采用的参数取决于哪个 "mode" 处于活动状态时。 通过一些示例代码更清晰: type Provider = "PROVIDER A" | "PR...
    程式設計 發佈於2024-11-07
  • 如何建立人力資源管理解決方案
    如何建立人力資源管理解決方案
    1. Understanding the Basics of Frappe and ERPNext Task 1: Install Frappe and ERPNext Goal: Get a local or cloud-based instance of ERP...
    程式設計 發佈於2024-11-07
  • 從週五黑客到發布:對創建和發布開源專案的思考
    從週五黑客到發布:對創建和發布開源專案的思考
    从周五补丁破解到发布:对创建和发布开源项目的思考 这是针对初学者和中级开发人员的系列的一部分,通过将他们的想法作为开源项目发布或引起兴趣。 这些想法是有偏见的和个人的。计划发布更多文章。通过分享一些思考,我希望能启发你做自己的项目 思考(此) 作为 Java 开发人员学习 Go l...
    程式設計 發佈於2024-11-07
  • 可以使用 constexpr 在編譯時確定字串長度嗎?
    可以使用 constexpr 在編譯時確定字串長度嗎?
    常數表達式最佳化:可以在編譯時確定字串長度嗎? 在最佳化程式碼的過程中,開發人員嘗試計算使用遞​​歸函數在編譯時計算字串文字的長度。此函數逐字元計算字串並傳回長度。 初步觀察:該函數似乎按預期工作,在運行時返回正確的長度並產生表明計算發生在編譯時的彙編程式碼。這就提出了一個問題:是否保證length...
    程式設計 發佈於2024-11-07
  • 在 Raspberry Pi 上執行 Discord 機器人
    在 Raspberry Pi 上執行 Discord 機器人
    Unsplash 上 Daniel Tafjord 的封面照片 我最近完成了一个软件工程训练营,开始研究 LeetCode 的简单问题,并觉得如果我每天都有解决问题的提醒,这将有助于让我负起责任。我决定使用按 24 小时计划运行的不和谐机器人(当然是在我值得信赖的树莓派上)来实现此操作,该机器人将执...
    程式設計 發佈於2024-11-07
  • 解鎖 JavaScript 的隱藏寶石:未充分利用的功能可提高程式碼品質和效能
    解鎖 JavaScript 的隱藏寶石:未充分利用的功能可提高程式碼品質和效能
    In the ever-evolving landscape of web development, JavaScript remains a cornerstone technology powering countless large-scale web applications. While...
    程式設計 發佈於2024-11-07
  • 為什麼透過非常量指標修改「const」變數看起來有效,但實際上並沒有改變它的值?
    為什麼透過非常量指標修改「const」變數看起來有效,但實際上並沒有改變它的值?
    透過非常量指標修改 const在 C 中,const 變數一旦初始化就無法修改。但是,在某些情況下,const 變數可能會被更改。考慮以下代碼:const int e = 2; int* w = (int*)&e; // (1) *w = 5; ...
    程式設計 發佈於2024-11-07
  • Android - 將 .aab 檔案上傳到 Play 商店時出錯
    Android - 將 .aab 檔案上傳到 Play 商店時出錯
    如果您遇到此錯誤,請按照以下步驟操作以確保與您的套件名稱和簽署金鑰保持一致: 確保 app.json 檔案中的套件名稱與您第一次上傳 .aab 檔案時所使用的套件名稱相符。 "android": { "permissions":["CAMERA","READ_EXTERNAL_STO...
    程式設計 發佈於2024-11-07
  • 如何使用 PHP 將 HTML 轉換為 PDF
    如何使用 PHP 將 HTML 轉換為 PDF
    (適用於 Windows 的指南。不適用於 Mac 或 Linux) (圖片來源) 在 PHP 中將 HTML 轉換為 PDF 的方法不只一種。您可以使用Dompdf或Mpdf;但是,這兩個庫的執行方式有所不同。 注意:本文中並未包含所有解決方案。 要使用這兩個函式庫,您將需要 Composer...
    程式設計 發佈於2024-11-07
  • C++ 會擁抱垃圾收集嗎?
    C++ 會擁抱垃圾收集嗎?
    C 中的垃圾收集:實現和共識的問題C 中的垃圾收集:實現和共識的問題雖然有人建議C 最終會包含垃圾收集器,但它仍然是爭論和持續發展的主題。要理解其中的原因,我們必須深入研究迄今為止阻礙其納入的挑戰和考慮因素。 實現複雜性在 C 中加入隱式垃圾收集是一個非-瑣碎的任務。該語言的低級性質和對指針的廣泛支...
    程式設計 發佈於2024-11-07
  • 如何有條件地刪除 MySQL 中的欄位?
    如何有條件地刪除 MySQL 中的欄位?
    使用 MySQL ALTER 進行條件列刪除使用 MySQL ALTER 進行條件列刪除MySQL 中的 ALTER 指令提供了一種從表中刪除列的簡單方法。但是,當指定列不存在時,其傳統語法 (ALTER TABLE table_name DROP COLUMN column_name) 會引發錯誤...
    程式設計 發佈於2024-11-07
  • 你應該了解的現代 CSS 樣式 4
    你應該了解的現代 CSS 樣式 4
    TL;DR: 本博客使用代码示例来探索 Web 开发的五种最佳 CSS 样式和功能:容器查询、子网格、伪类、逻辑属性和实验室颜色空间。它们增强响应能力、简化布局并提高设计一致性。 层叠样式表 (CSS) 是一种众所周知的用于设计网页样式的语言。使用 CSS,您可以通过添加空格来自定义 HTML 元素...
    程式設計 發佈於2024-11-07
  • 箭頭函數或父作用域何時定義函數的參數?
    箭頭函數或父作用域何時定義函數的參數?
    ES6 箭頭函數中的參數:官方說明在 ES6 箭頭函數中,arguments 關鍵字的行為一直是爭論的話題。一些瀏覽器和平台(例如 Chrome、Firefox 和 Node)偏離了最初的 TC39 建議,引發了對該規範正確解釋的質疑。 根據官方 ES6 規範,箭頭函數沒有其自身的定義。自己的參數在...
    程式設計 發佈於2024-11-07
  • 根據您提供的內容,以下是一些採用問題格式的潛在文章標題:

* 載入資料本地內文件存取被拒絕:如何排除和修復錯誤? 
* 為什麼要載入資料LOCA
    根據您提供的內容,以下是一些採用問題格式的潛在文章標題: * 載入資料本地內文件存取被拒絕:如何排除和修復錯誤? * 為什麼要載入資料LOCA
    LOAD DATA LOCAL INFILE 存取被錯誤拒絕:不允許使用的命令當利用MySQL 的LOAD DATA INFILE 執行PHP 腳本時,它可能會遇到錯誤“用戶訪問被拒絕...(使用密碼:是)”。常見的解決方法是切換到 LOAD DATA LOCAL INFILE,儘管這可能會導致同一...
    程式設計 發佈於2024-11-07
  • 如何在 Python 中檢查文字檔是否為空?
    如何在 Python 中檢查文字檔是否為空?
    確定文字檔案是否為空在程式設計領域,通常需要確定特定檔案是否包含任何資料或無效。本文深入探討了這樣一個問題:「我們如何確定文字檔案是否為空?」Python 作為一種多功能程式語言,為這個問題提供了一個簡單的解決方案。透過利用 os.stat() 函數,我們可以檢索有關檔案的屬性數組,包括其大小。 查...
    程式設計 發佈於2024-11-07

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3