Skip to content

A web application built on React that allows users to see recipes for different meals.

Notifications You must be signed in to change notification settings

mdnaeem95/Meals-Application

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Create A New Project in REPLIT login/register in the dashboard click "create" find react template click run change title in index.html Get Assets copy styles from /src/App.css copy README.md Global Styles Info Setup Structure create /src/components Favorites.jsx, Meals.jsx, Modal.jsx, Search.jsx create component (arrow function) setup basic return (component name) or my personal favorite "shake and bake" export default const Search = () => { return

Dude, where is my car

} export default Search import all of them in App.js setup following structure

import './App.css'

import Search from './components/Search' import Meals from './components/Meals' import Modal from './components/Modal' import Favorites from './components/Favorites'

export default function App() {

return (

) } comment out Search, Favorites, Modal export default function App() {

return (

{/* /}
{/
/}
{/
*/}
) } App Level State in App.js Context API 3rd Party State Management Library Redux, Redux-Toolkit,....... Context API Provider Context API

create context.js in the root context.jsx

import React, {useContext} from 'react'

const AppContext = React.createContext()

const AppProvider = ({ children }) => {

return ( <AppContext.Provider value="hello"> {children} </AppContext.Provider> ) }

export { AppContext, AppProvider } index.jsx

import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import { AppProvider } from './context' ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> </React.StrictMode> ) Consume Data /components/Meals.jsx

import {useContext} from 'react' import {AppContext} from '../context' const Meals = () => { const context = useContext(AppContext); console.log(context) return

Meals Component

}

export default Meals Custom Hook context.jsx

export const useGlobalContext = () => { return useContext(AppContext) } import {useGlobalContext} from '../context' const Meals = () => { const context = useGlobalContext() console.log(context) return

Meals Component

}

export default Meals Data Fetching where and how context.jsx

import React, { useContext,useEffect } from 'react'

const AppContext = React.createContext()

const AppProvider = ({ children }) => {

useEffect(()=>{ console.log('fetch data here')
},[])

return <AppContext.Provider value={{name:'john', role:'student'}}> {children} </AppContext.Provider> } fetch data (fetch api or axios), from any url in useEffect cb log result Fetch API Fetch API random user context.jsx

const AppProvider = ({ children }) => { useEffect(()=>{ const fetchData = async() =>{ try { const response = await fetch('https://randomuser.me/api/') const data = await response.json(); console.log(data) } catch (error) { console.log(error) } } fetchData() },[]) Meals DB utilize search engine "meals db", follow the link Meals DB get familiar with docs get two url's Search meal by name Lookup a single random meal (hint the "https://" is missing) setup two variables in context.jsx (allMealsUrl, randomMealUrl) and assign the corresponding values Get Meals By Name (with axios) Axios

install axios import in context.jsx refactor fetchData change name switch to axios add url parameter switch to allMealsUrl log response context.jsx

import React, { useState, useContext, useEffect } from 'react'

const AppContext = React.createContext()

import axios from 'axios' const allMealsUrl = 'https://www.themealdb.com/api/json/v1/1/search.php?s=' const randomMealUrl = 'https://www.themealdb.com/api/json/v1/1/random.php'

const AppProvider = ({ children }) => {

const fetchMeals = async (url) => {

try {
  const response = await axios(url)
 console.log(response)

}
catch (e) {
  console.log(e.response)
}

}

useEffect(() => { fetchMeals(allMealsUrl) }, []) State Variable (meals) and render import useState hook setup state variable (meals) set it equal to the meals from api (setMeals) pass it down to entire app (value prop) destructure meals in the Meals component iterate over meals log each meal render something (anything) on the screen import React, { useState, useContext, useEffect } from 'react'

const AppProvider = ({ children }) => { const [meals, setMeals] = useState([])

const fetchMeals = async (url) => {

try {
  const { data } = await axios.get(url)
  setMeals(data.meals)
}
catch (e) {

  console.log(e.response)
}

}

useEffect(() => {

  fetchMeals(allMealsUrl)

}, [])

return ( <AppContext.Provider value={{ meals }} > {children} </AppContext.Provider> ) } /components/Meals.jsx

import { useGlobalContext } from '../context'

const Meals = () => { const { meals } = useGlobalContext();

return

{meals.map((singleMeal) => { console.log(singleMeal) return

single meal

})}

}

export default Meals Meals Component - Display Card /components/Meals.jsx

import { useGlobalContext } from '../context'

const Meals = () => { const { meals } = useGlobalContext();

return

{meals.map((singleMeal) => { const { idMeal, strMeal: title, strMealThumb: image } = singleMeal return <img src={image} style={{width:'200px'}} className="img"} />
{title}
click me })}

}

export default Meals Meals CSS React Icons React Icons

install import set icon in like button Infinite Loop Feel free to just watch initial render (we invoke useEffect) inside useEffect cb, we fetch data and change value for meals it triggers re-render we repeat steps 2 and 3 Loading setup state variable "loading", with default value false set loading to true as a first thing in fetchMeals set loading to false as a last thing in fetchMeals add loading to value prop (pass it down) in Meals.jsx set condition for loading it needs to be before current return return Loading... if loading is true context.jsx

const AppProvider = ({ children }) => { const [meals, setMeals] = useState([]) const [loading, setLoading] = useState(false)

const fetchMeals = async (url) => { setLoading(true) try { const { data } = await axios.get(url) setMeals(data.meals) } catch (e) {

  console.log(e.response)
}
setLoading(false)

} return ( <AppContext.Provider value={{ loading, meals }} > {children} </AppContext.Provider> ) } /components/Meals.jsx

import { useGlobalContext } from '../context' import { BsHandThumbsUp } from 'react-icons/bs' const Meals = () => { const { loading, meals } = useGlobalContext();

if (loading) { return

Loading...

} } No items in fetchMeals check if data.meals is truthy returns true basically has some value only if data.meals has items set it as meals state value otherwise set meals variable as empty array in Meals.jsx check if meals length is less than 1 if that's the case return No items place it between loading and current return (cards) context.jsx const AppProvider = ({ children }) => { const [meals, setMeals] = useState([]) const [loading, setLoading] = useState(false)

const fetchMeals = async (url) => { setLoading(true) try { const { data } = await axios.get(url) if (data.meals) { setMeals(data.meals) } else { setMeals([]) } } catch (e) {

  console.log(e.response)
}
setLoading(false)

} return ( <AppContext.Provider value={{ loading, meals }} > {children} </AppContext.Provider> ) } /components/Meals.jsx

import { useGlobalContext } from '../context' import { BsHandThumbsUp } from 'react-icons/bs' const Meals = () => { const { loading, meals } = useGlobalContext();

if (loading) { return

Loading...

}

if (meals.length < 1) { return

No meals matched your search term. Please try again.

} } Search Component - Structure in Search.jsx import useState and useGlobalContext setup return header.search-container form input.form-input type="text" button.btn type="submit" button.btn.btn-hipster type="button" in App.jsx display Search Component /components/Search.jsx

import { useState } from 'react' import {useGlobalContext} from '../context'

const Search = () => {

return

search suprise me !

}

export default Search Search Component - CSS HandleChange and Handle Submit create "text" state variable create two functions handleChange and handleSubmit in the handleChange, grab e.target.value and set as text value add onChange to input and set it equal to handleChange in the handleSubmit set e.preventDefault() add onSubmit to form element and set it equal to handleSubmit Search.jsx

import { useState } from 'react' import {useGlobalContext} from '../context'

const Search = () => {

const [text, setText] = useState('')

const handleChange = (e) => { setText(e.target.value) } const handleSubmit = (e) => { e.preventDefault()

}

return

search suprise me !

}

export default Search Search Term in context.jsx create new state variable "searchTerm" with default value '' combine allMealsUrl with searchTerm and pass in the fetchMeals add searchTerm to useEffect's dependency array add setSearchTerm to value prop (pass it down) grab setSearchTerm in Search.jsx in the handleSubmit check setup a condition if the "text" has a value set it equal to "searchTerm" context.jsx

const AppProvider = ({ children }) => { const [meals, setMeals] = useState([]) const [loading, setLoading] = useState(false) const [searchTerm, setSearchTerm] = useState('')

const fetchMeals = async (url) => { setLoading(true) try { const { data } = await axios.get(url) if (data.meals) { setMeals(data.meals) } else { setMeals([]) } } catch (e) {

  console.log(e.response)
}
setLoading(false)

}

useEffect(() => { fetchMeals(${allMealsUrl}${searchTerm}) }, [searchTerm])

return ( <AppContext.Provider value={{ loading, meals, setSearchTerm }} > {children} </AppContext.Provider> ) } /components/Search.jsx

import { useState } from 'react' import {useGlobalContext} from '../context'

const Search = () => { const { setSearchTerm } = useGlobalContext() const [text, setText] = useState('')

const handleChange = (e) => { setText(e.target.value) } const handleSubmit = (e) => { e.preventDefault() if (text) { setSearchTerm(text)

}

}

return

search suprise me !

}

export default Search Fetch Random Meal context.jsx

const AppProvider = ({ children }) => {

const fetchRandomMeal = () => { fetchMeals(randomMealUrl) }

return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal}} > {children} </AppContext.Provider> ) } /components/Search.jsx

import { useState } from 'react' import {useGlobalContext} from '../context'

const Search = () => { const { setSearchTerm, fetchRandomMeal } = useGlobalContext() const [text, setText] = useState('')

const handleChange = (e) => { setText(e.target.value) } const handleSubmit = (e) => { e.preventDefault() if (text) { setSearchTerm(text)

}

}

return

search suprise me !

}

export default Search Fix Bugs /components/Search.jsx

import { useState } from 'react' import { useGlobalContext } from '../context'

const Search = () => { const { setSearchTerm, fetchRandomMeal } = useGlobalContext() const [text, setText] = useState('')

const handleChange = (e) => { setText(e.target.value) } const handleSubmit = (e) => { e.preventDefault() if (text) { setSearchTerm(text) } }

const handleRandomMeal = () => { setSearchTerm('') setText('') fetchRandomMeal() }

return

search suprise me !

}

export default Search context.jsx

const AppProvider = ({ children }) => {

useEffect(() => { fetchMeals(allMealsUrl) }, [])

useEffect(() => { if (!searchTerm) return fetchMeals(${allMealsUrl}${searchTerm}) }, [searchTerm])

return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal}} > {children} </AppContext.Provider> ) } Modal - Setup /components/Modal.jsx

import { useGlobalContext } from '../context'

const Modal = () => {

return

modal content here

}

export default Modal context.jsx

const AppProvider = ({ children }) => {

const [showModal, setShowModal] = useState(false)

return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal, showModal }} > {children} </AppContext.Provider> ) } App.jsx

import { useGlobalContext } from './context' import './App.css'

import Search from './components/Search' import Meals from './components/Meals' import Modal from './components/Modal' import Favorites from './components/Favorites' export default function App() { const { showModal } = useGlobalContext()

return (

  <Search />
  {/*<Favorites/>*/}

  <Meals />
  {showModal && <Modal />}
</main>

) } Modal CSS - Setup App.css

.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.85); display: grid; place-items: center; transition: var(--transition); z-index:100; } .modal-container{ width:80vw; max-width:800px; height:80vh; overflow:scroll; background:var(--white); border-radius:var(--borderRadius); } Display Meal in the Modal context.jsx

const AppProvider = ({ children }) => {

const [selectedMeal, setSelectedMeal] = useState(null)

const selectMeal = (idMeal, favoriteMeal) => { let meal;

  meal = meals.find((meal) => meal.idMeal === idMeal);

setSelectedMeal(meal);
setShowModal(true)

}

return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal, showModal, selectMeal, selectedMeal }} > {children} </AppContext.Provider> ) } /components/Meals.jsx

import { useGlobalContext } from '../context' import { BsHandThumbsUp } from 'react-icons/bs' const Meals = () => { const { loading, meals, selectMeal } = useGlobalContext();

if (loading) { return

Loading...

}

if (meals.length < 1) { return

No meals matched your search term. Please try again.

}

return

{meals.map((singleMeal) => { const { idMeal, strMeal: title, strMealThumb: image } = singleMeal return <img src={image} className="img" onClick={() => selectMeal(idMeal)} />
{title}
})}

}

export default Meals Display Selcted Meal and Close Modal context.jsx

const AppProvider = ({ children }) => {

const closeModal = () => { setShowModal(false) }

return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal, showModal, selectMeal, selectedMeal, closeModal}} > {children} </AppContext.Provider> ) } /components/Modal.jsx

import { useGlobalContext } from '../context'

const Modal = () => { const { selectedMeal, closeModal } = useGlobalContext()

const { strMealThumb: image, strMeal: title, strInstructions: text, strSource: source } = selectedMeal return

{title}

Cooking Instructions

{text}

Original Source close

}

export default Modal Modal CSS - Complete App.css

.modal-img{ height:15rem; border-top-left-radius:var(--borderRadius); border-top-right-radius:var(--borderRadius);

}

.modal-content{ padding:1rem 1.5rem; } .modal-content p{ color:var(--grey-600); } .modal-content a{ display:block; color:var(--primary-500); margin-bottom:1rem; text-decoration:underline; transition:var(--transition); } .modal-content a:hover{

color:var(--black); } .close-btn{ background:var(--red-dark); color:var(--white); } Favorites - Setup context.jsx

const AppProvider = ({ children }) => {

const [favorites, setFavorites] = useState([]);

const addToFavorites = (idMeal) => { const meal = meals.find((meal) => meal.idMeal === idMeal); const alreadyFavorite = favorites.find((meal) => meal.idMeal === idMeal); if (alreadyFavorite) return const updatedFavorites = [...favorites, meal] setFavorites(updatedFavorites) } const removeFromFavorites = (idMeal) => { const updatedFavorites = favorites.filter((meal) => meal.idMeal !== idMeal); setFavorites(updatedFavorites) } return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal, showModal, selectMeal, selectedMeal, closeModal, favorites, addToFavorites, removeFromFavorites }} > {children} </AppContext.Provider> ) } /components/Meals.jsx

import { useGlobalContext } from '../context' import { BsHandThumbsUp } from 'react-icons/bs' const Meals = () => { const { loading, meals, selectMeal, addToFavorites } = useGlobalContext();

if (loading) { return

Loading...

}

if (meals.length < 1) { return

No meals matched your search term. Please try again.

}

return

{meals.map((singleMeal) => { const { idMeal, strMeal: title, strMealThumb: image } = singleMeal return <img src={image} className="img" onClick={() => selectMeal(idMeal)} />
{title}
<button className='like-btn' onClick={() => addToFavorites(idMeal)}> })}

}

export default Meals Render Favorites App.jsx

import { useGlobalContext } from './context' import './App.css'

import Search from './components/Search' import Meals from './components/Meals' import Modal from './components/Modal' import Favorites from './components/Favorites' export default function App() { const { showModal, favorites } = useGlobalContext()

return (

  <Search />
  {favorites.length > 0 && <Favorites />}

  <Meals />
  {showModal && <Modal />}
</main>

) } /components/Favorites

import { useGlobalContext } from '../context'

const Favorites = () => { const { favorites, selectMeal, removeFromFavorites } = useGlobalContext()

return

Favorites
{favorites.map((item) => { const { idMeal, strMealThumb: image } = item;

      return <div key={idMeal} className="favorite-item" >
        <img src={image} className="favorites-img img" />
        <button className='remove-btn' onClick={() => removeFromFavorites(idMeal)}>remove</button>
      </div>
    })}
  </div>
</div>
}

export default Favorites Favorites CSS App.css

/* Favorites */

.favorites{ background:var(--black); color:var(--white); padding:1rem 0; }

.favorites-content{ width: var(--view-width); max-width: var(--max-width); margin:0 auto; } .favorites-container{ display:flex; gap:0.5rem; flex-wrap:wrap; } .favorite-item{ text-align:center; } .favorites-img{ width:60px; border-radius:50%; border:5px solid var(--white); cursor:pointer; } .remove-btn{ margin-top:0.25rem; background:transparent; color:var(--white); border:transparent; cursor:pointer; transition:var(--transition); font-size:0.5rem; } .remove-btn:hover{ color:var(--red-dark); } SelectMeal Refactor context.jsx

const selectMeal = (idMeal, favoriteMeal) => { let meal; if (favoriteMeal) { meal = favorites.find((meal) => meal.idMeal === idMeal); } else { meal = meals.find((meal) => meal.idMeal === idMeal); } setSelectedMeal(meal); setShowModal(true) } /components/Favorites.jsx

import { useGlobalContext } from '../context'

const Favorites = () => { const { favorites, selectMeal, removeFromFavorites } = useGlobalContext()

return

Favorites
{favorites.map((item) => { const { idMeal, strMealThumb: image } = item;

      return <div key={idMeal} className="favorite-item" >
        <img src={image} className="favorites-img img" onClick={() => selectMeal(idMeal, true)} />
        <button className='remove-btn' onClick={() => removeFromFavorites(idMeal)}>remove</button>
      </div>
    })}
  </div>
</div>
}

export default Favorites Add Favorites to Local Storage contex.jsx

const getFavoritesFromLocalStorage = () => { let favorites = localStorage.getItem('favorites'); if (favorites) { favorites = JSON.parse(localStorage.getItem('favorites')) } else { favorites = [] } return favorites }

const AppProvider = ({ children }) => {

const [favorites, setFavorites] = useState(getFavoritesFromLocalStorage());

const addToFavorites = (idMeal) => { const meal = meals.find((meal) => meal.idMeal === idMeal); const alreadyFavorite = favorites.find((meal) => meal.idMeal === idMeal); if (alreadyFavorite) return const updatedFavorites = [...favorites, meal] setFavorites(updatedFavorites) localStorage.setItem("favorites", JSON.stringify(updatedFavorites)) } const removeFromFavorites = (idMeal) => { const updatedFavorites = favorites.filter((meal) => meal.idMeal !== idMeal); setFavorites(updatedFavorites) localStorage.setItem("favorites", JSON.stringify(updatedFavorites)) } return ( <AppContext.Provider value={{ loading, meals, setSearchTerm, fetchRandomMeal, showModal, selectMeal, selectedMeal, closeModal, favorites, addToFavorites, removeFromFavorites }} > {children} </AppContext.Provider> ) }

About

A web application built on React that allows users to see recipes for different meals.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published