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>
);
}
}
Related
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
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>
);
}
So I want when I press the button in the Button Component everything in the 'li section' disappears as well as in the ImageComponent but it not working I would like to know what my mistake is. ButtonComponent is rendered somewhere else.
App Component/Parent
function App({ hideButton }) {
return (
<div className="App">
<ImageComponent hideButton={hideButton} />
</div>
);
}
// ButtonComponent
function ButtonComponent() {
const [hideButton, setHideButton] = React.useState(false)
function handleClick() {
setHideButton(true)
}
return (
{
!hideButton && (
<li>
<img className="image"src="./icons/>
<Button onClick={handleClick} variant="outlined" className="button__rightpage" >Hide</Button>
<caption className="text"> Hide</caption>
</li >
)
}
)
}
// ImageComponent
const ImageComponent = ({ hideButton }) => {
return (
<div>
{
!hideButton && (
<div>
<img src='icons/icon.png' />
<caption>Image </caption>
</div>
)
}
</div>
)
}
you need to lift up the state to the most parent Component be accessible to the ButtonCommponent and the ImageComponent. in this Case App Component. however, if the ButtonComponent is rendered out of the hierarchy under the App Component tree, you should use the context API.
create a context and share the state on it and it will be accessible on the application level.
//#1. create context.
export const HiddenContext = React.createContext(false);
//#2. create the provider and share the state with it.
function HiddenProvider({ children }) {
const [hideButton, setHideButton] = React.useState(false);
function handleClick() {
setHideButton(true);
}
return (
<HiddenContext.Provider value={{ hideButton, handleClick }}>
{children}
</HiddenContext.Provider>
);
}
//#3. render the provider component to the most top parent component
export default function App() {
const { hideButton } = React.useContext(HiddenContext);
return (
<HiddenProvider>
<div className="App">
<ImageComponent hideButton={hideButton} />
<OtherComponentRenderTheButton />
</div>
</HiddenProvider>
);
}
// other component that render the button
function OtherComponentRenderTheButton() {
return <ButtonComponent />;
}
//ButtonComponent
function ButtonComponent() {
const { hideButton, handleClick } = React.useContext(HiddenContext);
return (
<React.Fragment>
{!hideButton && (
<li>
<img className="image" src="./icons" alt="" />
<Button
onClick={handleClick}
variant="outlined"
className="button__rightpage"
>
Hide
</Button>
<caption className="text"> Hide</caption>
</li>
)}
</React.Fragment>
);
}
// ImageComponent
const ImageComponent = () => {
const { hideButton } = React.useContext(HiddenContext);
return (
<div>
{!hideButton && (
<React.Fragment>
<img src="icons/icon.png" alt="" />
<caption>Image </caption>
</React.Fragment>
)}
</div>
);
};
working demo
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>
)
}
I've created a React component that takes inputs from other components to display text of various size in a view. Since it is basically a form, what I want to do is pass the current view into another page where I will then post that view to my database as JSON.
Since the state of the input fields are not set in this component, I'm not sure how I would pass them as props to a new view.
This is a condensed version of what my data input component looks like:
INPUTSHOW.JSX
export default class InputShow extends Component {
componentDidMount() {
autosize(this.textarea);
}
render() {
const { node } = this.props;
...
return (
<div className="editor-div" >
{
(node.type === 'buttonA') ?
<textarea
style={hlArea}
ref={a => (this.textarea = a)}
placeholder="type some text"
rows={1}
defaultValue=""
id={node.id} className='editor-input-hl' type="text" onChange={this.props.inputContentHandler} />
:
(node.type === 'buttonB')
?
<textarea
style={subArea}
ref={b => (this.textarea = b)}
placeholder="type some text"
rows={1}
defaultValue=""
id={node.id} className='editor-input-sub' type="text" onChange={this.props.inputContentHandler} />
:
""
}
</div >
)
}
}
This works fine in creating inputs in a current view. I then pass those values to TextAreaField.JSX
export default (props) => {
return (
<>
<button><Link to={{
pathname: '/edit/preview',
text: props.inputsArray
}}>preview</Link></button>
<div className='view'>
{
props.inputsArray.map(
(node, key) => <InputShow key={key} node={node} inputContentHandler={props.inputContentHandler} />
)
}
</div>
</>
)
}
and then finally that is rendered in my Edit.JSX form:
export default class Edit extends React.Component {
constructor(props) {
super(props)
UniqueID.enableUniqueIds(this);
this.state = {
inputs: [],
text: ''
}
}
...
createPage = async () => {
await this.props.postPage(this.state.text)
}
// Handler for listen from button.
buttonCheck = (e) => {
index++;
const node = {
id: this.nextUniqueId() + index,
type: e.target.id,
text: '',
image: true
}
this.setState(
prev => ({
inputs: [...prev.inputs, node]
})
)
console.log(this.state.inputs);
}
inputContentHandler = (e) => {
let newArray = this.state.inputs;
let newNode = newArray.find((node) => {
return (node.id === e.target.id)
})
newNode.text = e.target.value;
this.setState({ inputs: newArray });
console.log(this.state.inputs);
}
render() {
return (
<div>
<InnerHeader />
<div className='some-page-wrapper'>
<div className='row'>
<div className="dash-card-sm">
<br />
<EditButtonContainer buttonCheck={this.buttonCheck} />
<Route path='/edit/form' render={() => (
<TextAreaField
inputsArray={this.state.inputs}
inputContentHandler={this.inputContentHandler}
/>
)}
/>
<Route path='/edit/preview' render={(props) => (
<Preview
inputs={this.state.inputs}
text={this.state.text}
createPage={this.createPage}
/>
)}
/>
<br /> <br />
{/* Button Header */}
</div>
</div>
</div>
</div>
)
}
}
The problem is that I don't know how I should be passing the rendered view to the Preview.jsxcomponent. I'm still new to react (4 months)...Any help in pointing me in the right direction would be appreciated.