Documentation Index Fetch the complete documentation index at: https://mintlify.com/Miguelcds/Recipe-Hub/llms.txt
Use this file to discover all available pages before exploring further.
Recipe Hub lets you search for recipes by name and browse random recipes on the home page. This page explains how search works end-to-end.
How it works
Type a recipe name
The SearchBar component renders a text input. As you type, the query is tracked in local state.
Submit the form
Pressing Search or hitting Enter triggers handleSubmit. If the input is empty, an error message appears instead of firing the request.
Results appear in the grid
The useRecipes hook calls the API and updates state. The RecipeList component renders a card for each result.
Search is case-insensitive. TheMealDB handles normalization on the API side, so “chicken”, “Chicken”, and “CHICKEN” all return the same results.
SearchBar component
SearchBar is a controlled input component. The parent (Home) owns the search string state and passes it down. The component itself is stateless — it simply renders the input and delegates all logic upward.
import "./SearchBar.css"
export const SearchBar = ({ search , setSearch , onSearch , error }) => {
return (
< div className = "search-superior" >
< div className = "search-container" >
< input
id = "search-input"
type = "text"
placeholder = "Search Recipes..."
value = { search }
onChange = { ( e ) => setSearch ( e . target . value ) }
/>
< button onClick = { onSearch } > Buscar </ button >
</ div >
{ error && < p > { error } </ p > }
</ div >
)
}
Empty search validation
Validation happens in Home before fetchRecipes is called. If the search string is empty or whitespace, Home sets a local error state and passes it to SearchBar as the error prop, which renders it below the input:
const handleClick = async () => {
if ( search . trim () === "" ) {
setApiError ( "" )
recipes . splice ( 0 , recipes . length )
return setError ( "Tienes que escribir algo si quieres hacer una busqueda" )
}
setError ( "" )
return fetchRecipes ( search )
}
useRecipes hook
The useRecipes hook owns all recipe state — the list, loading flag, and any API error. It exposes two fetch functions: fetchRecipes for search queries and fetchRandomRecipes for the home page on load.
import { useState } from 'react'
import { getRecipeByName , getRandomRecipes } from '../services/api'
const useRecipes = () => {
const [ recipes , setRecipes ] = useState ([])
const [ loading , setLoading ] = useState ( false )
const [ apiError , setApiError ] = useState ( '' )
const fetchRecipes = async ( search ) => {
setLoading ( true )
setRecipes ([])
setApiError ( '' )
try {
const data = await getRecipeByName ( search )
setRecipes ( data )
} catch ( error ) {
return setApiError ( error . message )
} finally {
setLoading ( false )
}
}
const fetchRandomRecipes = async () => {
setLoading ( true )
setApiError ( '' )
setRecipes ([])
try {
const data = await getRandomRecipes ()
setRecipes ( data )
} catch ( error ) {
setApiError ( error . message )
} finally {
setLoading ( false )
}
}
return { recipes , loading , apiError , fetchRecipes , fetchRandomRecipes , setApiError }
}
export default useRecipes
Random recipes on home page load
When the home page mounts, a useEffect calls fetchRandomRecipes automatically. This gives users something to browse without needing to search first.
const Home = () => {
const [ search , setSearch ] = useState ( "" )
const [ error , setError ] = useState ( "" )
const { recipes , loading , apiError , fetchRecipes , fetchRandomRecipes , setApiError } = useRecipes ()
useEffect (() => {
fetchRandomRecipes ()
}, [])
const handleClick = async () => {
if ( search . trim () === "" ) {
setApiError ( "" )
recipes . splice ( 0 , recipes . length )
return setError ( "Tienes que escribir algo si quieres hacer una busqueda" )
}
setError ( "" )
return fetchRecipes ( search )
}
return (
<>
< SearchBar search = { search } setSearch = { setSearch } error = { error } onSearch = { handleClick } />
{ loading && < p style = { { textAlign: "center" , fontSize: "16px" , color: "white" } } > Loading... </ p > }
{ apiError && < p style = { { textAlign: "center" , fontSize: "16px" , color: "red" } } > { apiError } </ p > }
< RecipeList recipes = { recipes } />
</>
)
}
The empty dependency array [] ensures fetchRandomRecipes runs once on mount. Any subsequent search replaces the random recipes with search results.
Recipe grid components
Search results are rendered by RecipeList, which maps over the recipes array and renders a RecipeCard for each entry.
RecipeList.jsx
RecipeCard.jsx
import RecipeCard from './RecipeCard'
import './RecipeList.css'
const RecipeList = ({ recipes }) => {
if ( ! recipes . length ) return null
return (
< div className = "recipe-list" >
{ recipes . map (( recipe ) => (
< RecipeCard
key = { recipe . id }
id = { recipe . id }
name = { recipe . name }
picture = { recipe . picture }
category = { recipe . category }
/>
)) }
</ div >
)
}
export default RecipeList
Each RecipeCard links to /recipe/:id, which opens the recipe detail page .
Recipe card data
Each object in the recipes array has the following shape:
Field Type Description idstring Unique recipe identifier namestring Recipe name picturestring URL of the recipe image categorystring Meal category (e.g. “Chicken”, “Dessert”)