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
You are overwriting the state with the API response instead of pushing the response to the state array.
Try changing
setRecipe(response.data)
tosetRecipe(prev => [...prev, response.data])
. This will maintain an array data type in state.See this page on the React docs for more details
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();
andrecipe
(singular) variable name indicate, don’t use arrays or.map
at all.In your render, change
singleRecipe
torecipe
(or rename state toconst [singleRecipse, setSingleRecipe] = useState(null)
) and conditionally render it, removing the<ul>
: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
or
which presumably returns an array of recipes which you can then overwrite the state with:
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 toRecipes
orAllRecipes
.You can use
null
or[]
as the default and conditionally render a loading message or an empty array message. For example: