skip to Main Content

I have this tricky question but dunno how to solve it. I tried to .map to print out some data today from the recipe which I have set as an empty array initially. However, an error popped up and it said, recipe.map is not a function, so I went to console.log (typeof .... and console.log lots of things. I found out

const [recipe, setRecipe] = useState([]);

so when I have objects in the recipe, I can apply .map to print out whatever the content is in it.

However when there is one object in the recipe, or there is one thing I want to print out from the recipe, the object would not be put in the array! It will just show up as an object and then I cannot apply .map on it since .map can only be applied on an array but I have actually set it to an empty array in the first place.

I have also tried

const [recipe, setRecipe] = useState([{}]);

const [recipe, setRecipe] = useState([{},{}]);

but none of these work. I expected outcome is there are multiple recipes in the list, and when I clicked into one of them, users will be redirected to the specific recipe.

import React from "react";
import axios from "axios";
import { useState } from "react";
import { useEffect } from "react";
import { useParams } from "react-router-dom";

const SingleRecipe = () => {
  const [recipe, setRecipe] = useState([]);
  const { recipeID } = useParams(); //useParams needs curly bracket and no need  []
  console.log(typeof recipe);
  console.log("this is recipeID" + recipeID);
  useEffect(() => {
    axios
      .get(`http://localhost:3001/recipe/detail/${recipeID}`)
      .then((response) => {
       
        setRecipe(response.data);
        console.log("this is useEffect in SingleRecipe..." + response.data);
       
      })
      .catch((err) => {
        if (err.response) alert(JSON.stringify(err.response.data));
      });
  }, []);

  return (
    <div className="recipeSession">
      <h1 className="recipeSessionHeader">Recipe</h1>
      <div className="recipesContainer">
        <div className="recipes">
          <ul className="recipeList">
            {recipe.map((singleRecipe) => (
              <li key={singleRecipe._id}>
                <div className="recipeName">
                  <h2 className="recipeNameHeader">{singleRecipe.name}</h2>
                </div>
                <div className="recipePhoto">
                  <img src={singleRecipe.imageUrl} alt={singleRecipe.name} />
                </div>
                <div className="recipeIngredients">
                  <p>{singleRecipe.ingredient}</p>
                </div>
                <div className="recipeInstruction">
                  <p>{singleRecipe.instruction}</p>
                </div>
                <div className="recipeCookingTime">
                  <p> Cooking Time: {singleRecipe.cookingTime}(minutes)</p>
                </div>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
};

export default SingleRecipe;

2

Answers


  1. You are overwriting the state with the API response instead of pushing the response to the state array.

    Try changing setRecipe(response.data) to setRecipe(prev => [...prev, response.data]). This will maintain an array data type in state.

    See this page on the React docs for more details

    Login or Signup to reply.
  2. There seems to be confusion about whether this component should render one or many recipes.

    If you intended this to be a "detail" view that shows a single recipe rather than an array of recipes as your component name, fetch call, const { recipeID } = useParams(); and recipe (singular) variable name indicate, don’t use arrays or .map at all.

    const [recipe, setRecipe] = useState(null);
    

    In your render, change singleRecipe to recipe (or rename state to const [singleRecipse, setSingleRecipe] = useState(null)) and conditionally render it, removing the <ul>:

    recipe ? (
      <div>{/* render the recipe */}</div>
    ) : (
      <div>Loading recipe...</div>
    )
    

    There’s no need for the <ul> and <li> for a single item.


    On the other hand, if you intended to use this component to list multiple recipes, change your fetch to

    .get(`http://localhost:3001/recipe`)
    

    or

    .get(`http://localhost:3001/recipes`)
    

    which presumably returns an array of recipes which you can then overwrite the state with:

    setRecipes(response.data);
    

    Notice I’ve pluralized recipes since it’s an array, keeping the intent clear.

    Remove const { recipeID } = useParams(); which is misleading and no longer used if your intent is to render multiple recipes. Rename the component to Recipes or AllRecipes.

    You can use null or [] as the default and conditionally render a loading message or an empty array message. For example:

    // ....
    
    if (!recipes) {
      return <div>Loading...</div>;
    }
    else if (recipes.length === 0) {
      return <div>No recipes yet</div>;
    }
    
    return (
      <ul>
        {recipes.map(e => (
          <li key={e._id}>
            {e.name}
            {/* render other relevant stuff ... */}
          </li>
        ))}
      </ul>
    );
    // ...
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search