>  기사  >  웹 프론트엔드  >  React를 사용하여 Recipe Finder 웹 사이트 구축

React를 사용하여 Recipe Finder 웹 사이트 구축

DDD
DDD원래의
2024-09-13 12:33:301193검색

Building Recipe Finder Website using React

소개

이 블로그에서는 React를 사용하여 Recipe Finder 웹사이트를 구축해 보겠습니다. 이 앱을 통해 사용자는 자신이 좋아하는 레시피를 검색하고, 인기 레시피나 새로운 레시피를 확인하고, 즐겨찾는 레시피를 저장할 수 있습니다. 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. Home.js, Trending.js, NewRecipe.js, RecipeDetail.js 등 API 호출이 이루어지는 페이지 내에 API 자격 증명을 추가하세요.

용법

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 (
    <>
      <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;

Home.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;

NewRecipe.js

이 페이지는 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;

Home.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;

Contact.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;

About.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: 에다맘
  • 아이콘: 반응 아이콘

작가

Abhishek Gurjar는 실용적이고 기능적인 웹 애플리케이션 제작에 열정을 쏟는 헌신적인 웹 개발자입니다. GitHub에서 더 많은 프로젝트를 확인해 보세요.

위 내용은 React를 사용하여 Recipe Finder 웹 사이트 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.