Skip to main content

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

1

Type a recipe name

The SearchBar component renders a text input. As you type, the query is tracked in local state.
2

Submit the form

Pressing Search or hitting Enter triggers handleSubmit. If the input is empty, an error message appears instead of firing the request.
3

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.
SearchBar.jsx
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:
Home.jsx
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.
useRecipes.js
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.
Home.jsx
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.
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:
FieldTypeDescription
idstringUnique recipe identifier
namestringRecipe name
picturestringURL of the recipe image
categorystringMeal category (e.g. “Chicken”, “Dessert”)

Build docs developers (and LLMs) love