I am new to react-hooks. I have successfully been able to load all objects coming from the api. But when I try to load a single post, it renders the object with id=1 only. I have 3 data in the backend database.
I am using Axios inside useEffect function.
My foodpage:
function Food() {
const [food, setFood] = useState([])
const [id,setId] = useState(1)
useEffect(() => {
axios.get(`https://texas-crm1.herokuapp.com/api/menus/${id}`)
.then(abc=>{
console.log(abc.data)
// console.log(abc.data.id);
setFood(abc.data)
})
.catch(err =>{
console.log(err)
})
}, [])
return (
<div>
<div className="food-page">
<PageHeader {...food} key={food.id} />;
<Customize />
<FoodDescription {...food} key={food.id} />;
</div>
</div>
);
}
export default Food;
Here, initially, i have put the state as id =1. And I am stuck how to change the state. Thats why on every food description only id=1 is rendered.
My food description:
function FoodDescription(props) {
const [quantity,setQuantity] = useState(0)
// let quantity = 0;
const handleDecrement = () => {
if (quantity > 0){
setQuantity((prev) => prev - 1);
}
else {
setQuantity(0);
}
};
const handleIncrement = () => {
setQuantity((prev) => prev + 1);
};
console.log(props);
const {food_name,long_title,subtitle,description,price,id} = props;
return (
<div className="food-description">
<div className="container">
<div className="title">
<div className="main-title">{food_name}</div>
</div>
<div className="description">
{/* {description.map((des: string, index: number) => { */}
{/* {description.map((des, index) => {
return <p key={index}>{des}</p>;
})} */}
{description}
{/* <div dangerouslySetInnerHTML="description">{description}</div> */}
</div>
<div className="order">
<div className="quantity">
{/* <div className="negative" onClick={() => this.handleDecrement()}>
-
</div>
{this.state.quantity}
<div className="positive" onClick={() => this.handleIncrement()}>
+
</div> */}
<div className="negative" onClick={handleDecrement}>-</div>
{quantity}
<div className="negative" onClick={handleIncrement}>
+
</div>
</div>
<ExploreButton active="link-active">
Add to Order - ${price}
</ExploreButton>
</div>
</div>
</div>
)
}
export default FoodDescription;
Here are three different foods. But when I click on anyone of them, only id=1 is rendered in Fooddescription page.
Update:
Components that contains the click button on Buy Now.
My MenuComponent.txs
const MenuComponent = (props: any) => {
console.log(props);
const {id,category, image,price,rating,food_name,description} = props;
// const starterMenu = starter.map
const starterMenu = [
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
];
const renderMenuList = () => {
switch (props.category) {
case "starters":
return <Starters />;
case "main courses":
return <MainCourses />;
case "soups & salads":
return <SoupsSalads />;
case "sliders":
return <Sliders />;
default:
break;
}
};
return (
<div className="menu-component">
<div className="title">
<div className="main-title">{props.category}</div>
</div>
<div className="menu-cards">
{starterMenu.map((starterItem, index) => {
return <MenuCard {...starterItem} key={index} />;
})}
</div>
{renderMenuList()}
</div>
);
};
export default MenuComponent;
The props are passed to MenuCard. My MenuCard is as follows:
const MenuCard = (props: any) => {
console.log(props);
return (
// Menu card
<div className="menu-card">
<div className="container">
{/* Thumbnail */}
<div className="thumbnail">
<img src={props.thumbnail} alt={props.title} />
</div>
{/* Title */}
<div className="title">{props.title}</div>
{/* Star rating */}
<div className="rating">
<StarRating rating={props.rating} />
</div>
{/* description */}
<div className="description">{props.description}</div>
<div className="bottom">
{/* price */}
<div className="price">{props.price}</div>
<Link to={`/menu/${props.id}`}>
<MenuButton highlighted="highlighted">Buy Now</MenuButton>
</Link>
</div>
</div>
</div>
);
};
export default MenuCard;
Update:2
When i go to menu it shows all the food items, And when I click on one of the Buy, now it shows correct id in the url. But still id=1 food details are shown.
I assume when you click Buy Now you hit the route /menu/:someId which renders the <Food />.
Since the selected menu's id is in the URL we can make use of the useParams hook to get the id and fire the API call. Your current code doesn't work because irrespective of the Menu Card you clicked you will always fire the API call for the id 1 . As you have hardcoded it . So to make it dynamic you can do this
import { useParams } from 'react-router-dom';
function Food() {
const [food, setFood] = useState([]);
// if your route is /menu/:menuId
const { menuId } = useParams();
useEffect(() => {
axios
.get(`https://texas-crm1.herokuapp.com/api/menus/${menuId}`)
.then((abc) => {
console.log(abc.data);
// console.log(abc.data.id);
setFood(abc.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div>
<div className="food-page">
<PageHeader {...food} key={food.id} />;
<Customize />
<FoodDescription {...food} key={food.id} />;
</div>
</div>
);
}
Reference
useParams Hook
Related
My goal was to fetch posts from Graphcms and populate an array - posts, and populate it into postlist, then the main component will change according to what the user clicks on a post from postlist, I can see the posts array is populated , but when i click on a post on postlist I get the following error
Main.js:22 Uncaught TypeError: Cannot read properties of undefined (reading 'featuredImage')
Below my files
App.js
function App() {
const [selectedPost,setSelectedPost] = useState(0);
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
const { posts } = await request(
'https://api-ap-southeast-2.graphcms.com/v2/ckxo1np9m5kw601xpccps4lrn/master',
`
{
posts {
id
title
slug
excerpt
featuredImage
{
url
}
}
}
`
);
console.log("print posts " , posts)
setPosts(posts);
};
fetchPosts();
}, []);
return ( <div className='app'>
<Header/>
{
posts.length>0 && (<>
<Main posts={posts} selectedPost={selectedPost}/>
<PostList posts={posts} setSelectedPost={setSelectedPost} />
</>
)
}
</div>
)
}
export default App;
And the Main.js Component
const Main = ({selectedPost,posts}) => {
const[activePost,setActivePost] =useState(posts[0])
console.log("activePost ", activePost)
useEffect(()=>{
setActivePost(posts[selectedPost])
},[posts,selectedPost])
return (
<div className='main'>
<div className='mainContent'>
<div className='postHighlight'>
<div className='postContainer'>
<img
className='selectedPost'
src= {activePost.featuredImage.url}
alt=''
/>
</div>
</div>
<div className='postDetails' style={{color:'#fff'}}>
<div className='title'>
{activePost.title} </div>
<span className='itemNumber'></span>
<span className='postExcerpt'>{activePost.excerpt}</span>
</div>
<div className='otherDetails'>
</div>
</div>
</div>
)
}
export default Main
And then we have postList.js file
const PostList = ({posts,setSelectedPost}) => {
return (
<div className='postList'>
{posts.map(post=>(
<div onClick={()=>setSelectedPost(post.id)}>
<CollectionCard key={post.slug} title={post.title} excerpt={post.excerpt} imageSrc={post.featuredImage.url}/>
</div>
)) })
</div>
)
}
export default PostList
Based on your app, you are using the index of the selected post.
The error arises from your onclick function. You are passing post.id to setSelectedPost() so you are accessing the posts array incorrectly. Hence, the undefined.
Just use the current index on your map function:
<div className='postList'>
{posts.map((post, index) => (
<div onClick={() => setSelectedPost(index)} key={post.slug}>
<CollectionCard
title={post.title}
excerpt={post.excerpt}
imageSrc={post.featuredImage.url}
/>
</div>
))
}
</div>
I have a small restaurant app which lets users order from a menu. The menu has three types: food, drink, dessert. The component structure from top to bottom is Order -> Menu -> MenuItem. I want to separate the menu page based on type (example: all food items are under a title called FOOD and so on). Right now, the Menu component receives the menu array as a prop from Order and renders MenuItem for each item in the array. Each item has a property called type. I omitted certain parts of the code unrelated to this issue for brevity.
//Order
export default function Order() {
const [menu, setMenu] = useState<Array<{}>>([]);
const [total, setTotal] = useState(0);
useEffect(() => {
apiFetch("menu").then((json) => setMenu(json.menu));
}, []);
async function handleSubmit(e: any) {
e.preventDefault();
const selectedItems = getSelectedItems(TotalStore);
apiFetch("order", "post", { selectedItems })
.then((json) => {
alert("Order has been submitted");
setTotal(0);
TotalStore.reset();
localStorage.setItem("last_order_id", json.order.id);
function checkOrderStatus() {
apiFetch(
`order/${json.order.id || localStorage.getItem("last_order_id")}`
).then((placedOrder) => {
const { order } = placedOrder;
if (order[0].status === 2) {
alert("Your order is ready!");
} else {
setTimeout(checkOrderStatus, 5000);
}
});
}
checkOrderStatus();
})
.catch((error) => {
alert("Server error");
});
}
function orderPlaced(total: number) {
return total !== 0 ? true : false;
}
return (
<div>
{menu.length > 0 ? (
<>
<div className="menu">
<div className="menu-title">Food Menu</div>
<form id="menu-form" onSubmit={handleSubmit} autoComplete="off">
<Menu onChange={itemChanged} props={menu} />
<button type="submit" disabled={!orderPlaced(total)}>
Place Order
</button>
</form>
</div>
<div className="order-total">
<h2>
Total: $<span>{total.toFixed(2)}</span>
</h2>
</div>
</>
) : (
<>Loading Menu</>
)}
</div>
);
}
//Menu
export default function Menu({ onChange, props }: MenuProps) {
return (
<div>
{props.map((food: any, index: number) => {
return (
<MenuItem
key={index}
onChange={onChange}
type={food.type}
item={food}
/>
);
})}
</div>
);
}
//MenuItem
export default function MenuItem({ onChange, item, type }: MenuItemProps) {
return (
<div>
<article className="menu-item" data-item-type={type}>
<h3 className="item-name">{item.name}</h3>
<input
type="number"
className="menu-item-count"
min="0"
value={data.count}
onChange={menuItemCountChange}
/>
<strong className="item-price">${item.price.toFixed(2)}</strong>
</article>
</div>
);
}
Here is what the page currently looks like:
You want to group your menu data by the food.type property. One way would be to sort the menu items into their food type "category, then rendering each group separately.
export default function Menu({ onChange, items }) {
const foodCategories = items.reduce((categories, item) => {
if (!categories[item.type]) {
categories[item.type] = []; // <-- new array for category type
}
categories[item.type].push(item); // <-- push item into category type
return categories;
}, {});
return (
<div>
{Object.entries(foodCategories).map(([type, foodItems]) => (
<div key={type}>
<h1>{type}</h1> // <-- category header
{foodItems.map((food, index) => ( // <-- map food items
<MenuItem
key={index}
onChange={onChange}
type={food.type}
item={food}
/>
))}
<div>
))}
</div>
);
}
I am currently making a project over the database I created using Mock API. I created a button, created addToFavorites function. When the button was clicked, I wanted the selected product's information to go to the favorites, but I couldn't. I would be glad if you could help me on how to do this.
(Favorites.js empty now. I got angry and deleted all the codes because I couldn't.)
(
Recipes.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import "./_recipe.scss"
import Card from "../Card"
function Recipes() {
const [recipes, setRecipes] = useState([])
const [favorites, setFavorites] = useState([])
useEffect(() => {
axios
.get("https://5fccb170603c0c0016487102.mockapi.io/api/recipes")
.then((res) => {
setRecipes(res.data)
})
.catch((err) => {
console.log(err)
})
}, [])
const addToFavorites = (recipes) => {
setFavorites([...favorites, recipes])
console.log("its work?")
}
return (
<div className="recipe">
<Card recipes={recipes} addToFavorites={addToFavorites} />
</div>
)
}
export default Recipes
Card.js
import React, { useState } from "react"
import { Link } from "react-router-dom"
import { BsClock, BsBook, BsPerson } from "react-icons/bs"
function Card({ recipes, addToFavorites }) {
const [searchTerm, setSearchTerm] = useState("")
return (
<>
<div className="recipe__search">
<input
type="text"
onChange={(event) => {
setSearchTerm(event.target.value)
}}
/>
</div>
<div className="recipe__list">
{recipes
.filter((recipes) => {
if (searchTerm === "") {
return recipes
} else if (
recipes.title.toLowerCase().includes(searchTerm.toLowerCase())
) {
return recipes
}
})
.map((recipe) => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={350} height={230} />
<h1 className="recipe__card__title">{recipe.title}</h1>
<h3 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h3>
<h3 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h3>
<button type="button" className="recipe__card__cta">
<Link
to={{
pathname: `/recipes/${recipe.id}`,
state: { recipe }
}}
>
View Recipes
</Link>
</button>
<button onClick={() => addToFavorites(recipes)}>
Add to favorites
</button>
</div>
)
})}
</div>
</>
)
}
export default Card
Final Output:
I have implemented the addToFavorite() and removeFavorite() functionality, you can reuse it the way you want.
I have to do bit of modification to the code to demonstrate its working, but underlying functionality of addToFavorite() and removeFavotie() works exactly the way it should:
Here is the Card.js where these both functions are implemented:
import React, { useState } from "react";
import { BsClock, BsBook, BsPerson } from "react-icons/bs";
function Card({ recipes }) {
const [searchTerm, setSearchTerm] = useState("");
const [favorite, setFavorite] = useState([]); // <= this state holds the id's of all favorite reciepies
// following function handles the operation of adding fav recipes's id's
const addToFavorite = id => {
if (!favorite.includes(id)) setFavorite(favorite.concat(id));
console.log(id);
};
// this one does the exact opposite, it removes the favorite recipe id's
const removeFavorite = id => {
let index = favorite.indexOf(id);
console.log(index);
let temp = [...favorite.slice(0, index), ...favorite.slice(index + 1)];
setFavorite(temp);
};
// this variable holds the list of favorite recipes, we will use it to render all the fav ecipes
let findfavorite = recipes.filter(recipe => favorite.includes(recipe.id));
// filtered list of recipes
let filtered = recipes.filter(recipe => {
if (searchTerm === "") {
return recipe;
} else if (recipe.title.toLowerCase().includes(searchTerm.toLowerCase())) {
return recipe;
}
});
return (
<div className="main">
<div className="recipe__search">
<input
type="text"
onChange={event => {
setSearchTerm(event.target.value);
}}
/>
</div>
<div className="recipe-container">
<div className="recipe__list">
<h2>all recipes</h2>
{filtered.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p>
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => addToFavorite(recipe.id)}>
add to favorite
</button>
</div>
);
})}
</div>
<div className="favorite__list">
<h2>favorite recipes</h2>
{findfavorite.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => removeFavorite(recipe.id)}>
remove favorite
</button>
</div>
);
})}
</div>
</div>
</div>
);
}
export default Card;
Here is the live working app : stackblitz
You can get the previous favourites recipes and add the new ones.
const addToFavorites = (recipes) => {
setFavorites(prevFavourites => [...prevFavourites, recipes])
console.log("its work?")
}
I want to dynamically update state CurrentIndex whenever I select a user.Right now I am hardcoding it to 0. I want to change that.
I want to update currentIndex whenever I click on the list of users.
SidePanel contains arrays of user and its outputting firstname,lastname
class Home extends Component {
state = {
loadUsers: [],
currentIndex: 0,
};
async componentDidMount() {
const res = await axios.get("http://localhost:5000/users");
this.setState({ loadUsers: res.data });
}
render() {
return (
<Fragment>
<div className="row">
<div className="col-md-3" style={{ backgroundColor: "#303F9F" }}>
<Typography variant="h6">List all Counsellors</Typography>
{this.state.loadUsers.map((user) => {
const { _id, firstname, lastname } = user;
return (
<div key={_id}>
<SidePanel
id={_id}
firstname={firstname}
lastname={lastname}
/>
</div>
);
})}
</div>
<div className="col-md-4">
{this.state.loadUsers.length > 1 && (
<div>
<MiddlePanel
user={this.state.loadUsers[this.state.currentIndex]}
/>
</div>
)}
</div>
</div>
</Fragment>
);
}
}
Create a handler to consume the mapped index and pass/attach handler where you need it.
class Home extends Component {
state = {
loadUsers: [],
currentIndex: 0,
};
...
incrementIndex = (currentIndex) => (e) => this.setState({ currentIndex });
render() {
return (
<Fragment>
<div className="row">
<div className="col-md-3" style={{ backgroundColor: "#303F9F" }}>
...
{this.state.loadUsers.map((user, index) => {
const { _id, firstname, lastname } = user;
return (
<div key={_id}>
<SidePanel
id={_id}
firstname={firstname}
lastname={lastname}
onClick={incrementIndex(index)} // <-- pass handler to Panel
/>
</div>
);
})}
</div>
...
</div>
</Fragment>
);
}
}
Appreciate your kind help!
What I'm trying to achieve is that on click of cardlist component it has to open respective product detail page just like e-commerce website.
I have created :
CardList component, card component, data(where it contain array of
object) and singleCardComponent(i.e the description page component)
I think I had made a mistake in cardList component.
I have got stuck on the logic how i will redirect to respective product page.
//--------------------card component--------------------
class Card extends Component {
render() {
return (
<div className='col-md-3 col-10 mx-auto mb-4'>
<div className="card">
<img src={this.props.imgsrc} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">Rs {this.props.price}</h5>
<p className="card-text">{this.props.title}</p>
<p className='card-date d-flex justify-content-end'>Oct 29</p>
</div>
</div>
</div>
)
}
}
//--------------------cardlist component--------------------
class CardList extends Component {
show_component = (i) => {
Data.map((v) => {
return <SingleCardComp
key={i}
imgsrc={v.imgsrc}
price={v.price}
title={v.title}
seller={v.seller_desc}
desc={v.description}
loc={v.location}
/>
})
}
render() {
return (
<div className='row'>
{
Data.map((val, i) => {
return <button onClick={()=>this.show_component(i)}>
<Card
key={i}
imgsrc={val.imgsrc}
price={val.price}
title={val.title}
/>
</button>
})
}
</div>
)
}
}
//--------------------Data--------------------
const Data = [
{
imgsrc: image0,
title: "Samsung A50",
price: 35500,
seller_desc: 'Bilal',
description: "Lorem, ipsum dolor sit",
location:'Kansas'
}
];
//--------------------SingleCardComp--------------------
class SingleCardComp extends Component {
render() {
return (
<div>
<img src={this.props.imgsrc} alt="..." />
<h5>Rs {this.props.price}</h5>
<p >{this.props.title}</p>
<h1>
Description:{this.props.desc}
</h1>
<h1>
Seller Details:{this.props.seller}
</h1>
<h1>
Posted in:{this.props.loc}
</h1>
</div>
)
}
}
Here is the image of card
The show_component method seems the problem here,
What is the Data means in the show_component method? Is that the same array used in CardList->render method? If that is the case what you need to do is update your show_component method like this,
show_component = (i) => {
Data.map((v, index) => {
if(index === i) {
return <SingleCardComp
key={i}
imgsrc={v.imgsrc}
price={v.price}
title={v.title}
seller={v.seller_desc}
desc={v.description}
loc={v.location}
/>
}
})
}
This is the solution with minimum change. However, the better solution would be to pass the card data in the show_component method as parameter. Like this,
state = {
selectedItem: null,
showSingleCardComp: false,
selectedItemIndex: 0,
}
show_component = (v, i) => {
this.setState({selectedItem: v, showSingleCardComp: true, selectedItemIndex: i});
}
And call the show_component method like this,
render() {
return (
<div className='row'>
{
Data.map((val, i) => {
return <button onClick={()=>this.show_component(val, i)}>
<Card
key={i}
imgsrc={val.imgsrc}
price={val.price}
title={val.title}
/>
</button>
})
}
{this.state.showSingleCardComp && (
<SingleCardComp
key={this.state.selectedItemIndex}
imgsrc={this.state.selectedItem.imgsrc}
price={this.state.selectedItem.price}
title={this.state.selectedItem.title}
seller={this.state.selectedItem.seller_desc}
desc={this.state.selectedItem.description}
loc={this.state.selectedItem.location}
/>
)}
</div>
)
}