How to change specific item button name by clicking the button in ReactJs? - javascript

I have multiple item cards on a page, I want to change only a specific item/card button " Add Favt" to "Remove Favt" when the user clicks on the "Add Favt" button. But in my case, all of the card button names change when clicked on only one card button.
Here is my approach:
const Listitem = ({ posts }) => {
const [btn, setBtn] = useState('Add Favt');
var arr = [];
const click = (index) => {
arr.push(posts[index]);
console.log(arr);
localStorage.setItem('items', JSON.stringify({ arr }));
if (btn === 'Add Favt') {
setBtn('Remove Favt');
} else {
setBtn('Add Favt');
}
};
return (
<div className="fav-content">
<ul className="card">
{posts.map((item, index) => {
console.log(item._id);
return (
<li key={item._id}>
<button onClick={() => click(index)}>{btn}</button>
<div className="post">
<h1>Name: {item.name}</h1>
<p>Bio: {item.bio}</p>
<a href={item.link}>Link: {item.link}</a>
</div>
</li>
);
})}
</ul>
</div>
);
};
How to solve this problem?

This may be one possible solution to achieve the desired objective.
Code Snippet
Please view the snippet in Full Page
const {useState} = React;
const Listitem = ({posts, ...props}) => {
// track whether each card is is favorite or not
const [isFavorite, setIsFavorite] = useState({});
// when button clicked, flip card from favorite
const handleClick = e => {
const id = e.target.id;
setIsFavorite(prev => ({
...prev,
[id]: !prev[id]
}))
};
return (
<div className="fav-content">
List of favorites: {
posts
.filter(({_id}) => [_id] in isFavorite && isFavorite[_id])
.map(({name}) => name)
.join()
}
<ul className="card">
{posts.map(item => (
<li key={item._id}>
<button
id={item._id}
onClick={handleClick}
>
{
isFavorite[item._id]
? 'Remove Fav'
: 'Add Fav'
} {item.name}
</button>
<div className="post">
<h4>Name: {item.name}</h4>
<p>Bio: {item.bio}</p>
<a href={item.link}>Link: {item.link}</a>
</div>
</li>
)
)}
</ul>
</div>
);
};
const itemsList = [
{_id: 1, name: 'item 1', bio: 'bio 1', link: 'link 1'},
{_id: 2, name: 'item 2', bio: 'bio 2', link: 'link 2'},
{_id: 3, name: 'item 3', bio: 'bio 3', link: 'link 3'},
{_id: 4, name: 'item 4', bio: 'bio 4', link: 'link 4'},
];
ReactDOM.render(
<div>
DEMO
<Listitem posts={[...itemsList]}/>
</div>,
document.getElementById("rd")
);
.fav-content { width: fit-content; padding: 5px 15px; }
.card { background-color: #DDDDFF; margin: 5px 15px; }
.post { background-color: #FFFFDD; margin: 5px 15px; }
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
Inline comments have been provided in the above code snippet.

You are using btn state variable for each button. setting btn in state will reflect in all of them. Make a separate component for this.
<button onClick={() => click(index)}>{btn}</button>
<div className="post">
<h1>Name: {item.name}</h1>
<p>Bio: {item.bio}</p>
<a href={item.link}>Link: {item.link}</a>
</div>
Maintain a local state in new component for status of individual item.

Related

Why am I unable to target a mapped out <li> from the button mapped out with it with React?

I am in the process of learning react and am working on a pretty simple food ordering app that maps through a list of meals in a component named AvailableMeals and would allow a user to hit the add to cart button on each mapped out item to add it to cart.
Right now though, I am unable to get my add button to target the LI that it is connected to. I have tried putting the targeting function in multiple places and tried to target different things (this, this.name, e.target.value, etc) but everything still comes up with undefined or throws an unable to read undefined error. I have even tried to use useContext, but still nothing. I will add the components that are relevant to this below.
My component tree is MealForm -> MealItem -> MealItemForm -> Input and will paste the markup here in that order.
Thanks in advance for any help.
const MealForm =(props) => {
return (
<ul className='mealForm'>
<MealItem meals={AvailableMeals} />
</ul>
)
}
const MealItem =(props) => {
// const mealsCtx = useContext(AvailableMeals);
const [cart, setCart] = useState(null)
const [curMeal, setCurMeal] = useState(null)
const addToCartHandler = (e, props) => {
e.preventDefault();
console.log(this); //This is what I am using to try and target
}
if ( props.meals.length === 0) {
return( <h2>No meals found!!</h2>)
} else{
return (
<Card >
{AvailableMeals.map((meal)=> {
return<>
<ul className='mealItem'>
<div className='mealItem-info'>
<li>{meal.name}</li>
<li>{meal.description}</li>
<li>{meal.price}</li>
<MealItemForm
id={meal.id}
key={meal.id}
name={meal.name}
description={meal.description}
onSub={addToCartHandler}
/>
</div>
</ul>
</>
})};
</Card>
)
}
};
const MealItemForm = (props) => {
return(
<form onSubmit={props.onSub}>
<Input label="Amount " input={{
id: 'amount_' + props.id,
type: 'number',
min: '1',
max: '10',
step: '1',
defaultValue: '1'
}}
/>
<button> + Add</button>
</form>
)
}
const Input = (props) => {
return (
<div >
<label htmlFor={props.input.id}>{props.label}</label>
<input {...props.input}/>
</div>
)
}
One is able to use stack-snippets to provide a working demo of one's code. This answer addresses the OP's question in trying to identify which meal's click event was invoked.
Changes significant to the question:
On the component: MealItemForm, changed below:
<form name={props.name} onSubmit={props.onSub}>
On the component: MealItem, changed addToCartHandler method:
console.log('mean-name corresponding to the clicked-item: ', e.target.name);
Impact observed:
The console.log properly displays the meal.name corresponding to the item's whose button was clicked.
Code Snippet
const {useState} = React;
const Input = (props) => {
return (
<div >
<label htmlFor={props.input.id}>{props.label}</label>
<input {...props.input}/>
</div>
)
};
const MealItemForm = (props) => {
return(
<form name={props.name} onSubmit={props.onSub}>
<Input label="Amount " input={{
id: 'amount_' + props.id,
type: 'number',
min: '1',
max: '10',
step: '1',
defaultValue: '1'
}}
/>
<button> + Add</button>
</form>
)
};
const MealItem = (props) => {
const [cart, setCart] = useState(null);
const [curMeal, setCurMeal] = useState(null);
const addToCartHandler = (e, props) => {
e.preventDefault();
console.log('mean-name corresponding to the clicked-item: ', e.target.name);
};
if ( props.meals.length === 0) {
return (<h2>No meals found!!</h2>)
} else {
return (
<div>
{
props.meals.map(meal => {
return (
<div>
<ul className='mealItem'>
<div className='mealItem-info'>
<li>{meal.name}</li>
<li>{meal.description}</li>
<li>{meal.price}</li>
<MealItemForm
id={meal.id}
key={meal.id}
name={meal.name}
description={meal.description}
onSub={addToCartHandler}
/>
</div>
</ul>
</div>
)
})
}
</div>
)
}
};
const AvailableMeals = [
{name: 'name 0', description: 'description 0', price: 'price 0', id: '0' },
{name: 'name 1', description: 'description 1', price: 'price 1', id: '1' },
{name: 'name 2', description: 'description 2', price: 'price 2', id: '2' },
{name: 'name 3', description: 'description 3', price: 'price 3', id: '3' },
{name: 'name 4', description: 'description 4', price: 'price 4', id: '4' }
];
const MealForm =(props) => {
return (
<ul className='mealForm'>
<MealItem meals={AvailableMeals} />
</ul>
)
};
ReactDOM.render(
<div>
DEMO
<MealForm />
</div>,
document.getElementById("rd")
);
.mealItem-info {
display: flex; border: 2px solid grey; width: fit-content;
align-items: center;
justify-content: space-around;
margin: 5px;
}
.mealItem-info>li { padding: 20px; margin-left: 25px; }
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

in a map function, i want to set a state which let me change the background color when i click on a div

So here's my problem, i map some data i receive from the back, it returns a group of div, i would like to be able to click a div, change his color background and use it in total price (they're options you can choose).
i tried to put a state "clicked" which set true on click, but the state is on all element ans not the only one i just clicked. After if my state is true, i change the background color and add it to the total price (calculated in the modal in details)
<p className="title-config">Configuration</p>
{data &&
data.additionalCharges.map((charges, index) => {
// console.log("charges.map", charges);
return (
<div
className={
clicked === true ? "clicked-config" : "unclicked-config"
}
key={index}
onClick={() => setClicked(true)}
>
<p>{charges.title}</p>
<p>{charges.description}</p>
<p>
{charges.price.amount} {location.state.price.currency}
</p>
</div>
);
})}
</div>
<div className="colonne2-config">
<div>
<span> Total {location.state.total}</span>
<span>{location.state.price.amount}</span>
</div>
<div>
<div onClick={() => setShowModal(true)}>Voir les details du prix</div>
<Modal
isOpen={showModal}
onRequestClose={() => setShowModal(false)}
style={{
overlay: {
backgroundColor: "lightgrey",
backgroundOpacity: "50%",
},
}}
>
<h1>Details du prix</h1>
<button onClick={() => setShowModal(false)}> X </button>
</Modal>
</div>
Here is a working example to achieve the desired objective:
Code Snippet
const {useState} = React;
const SomeComponent = ({data, ...props}) => {
// the clicked is being used to achieve two goals
// 1. track which item is clicked (ie, selected)
// 2. update the total-price by adding / subtracting the clicked item's price
// NOTE: This is not a good approach to employ in general. Please avoid.
// Instead, use a separate variable to calculate the total-price.
const [clicked, setClicked] = useState({total: 0});
const getClass = idx => (`item ${clicked[idx] ? 'selected' : 'unselected'}`);
return (
<div>
<h4>List of Items</h4>
{
data && Array.isArray(data) && data.map(
({title, description, amount}, idx) => (
<div
key={idx}
onClick={() => setClicked(prev => ({
...prev,
total: (
prev[idx] ? prev.total - +amount : prev.total + +amount
),
[idx]: !prev[idx]
}))}
class={getClass(idx)}
>
{title}   {description}   {amount}
</div>
)
)
}
<br/>
Total Price: {clicked.total}
</div>
);
};
const rawData = [
{title: 'Title 00', description: 'Description 00', amount: '100'},
{title: 'Title 01', description: 'Description 01', amount: '110'},
{title: 'Title 02', description: 'Description 02', amount: '120'},
{title: 'Title 03', description: 'Description 03', amount: '130'},
{title: 'Title 04', description: 'Description 04', amount: '140'},
{title: 'Title 05', description: 'Description 05', amount: '150'},
{title: 'Title 06', description: 'Description 06', amount: '160'}
];
ReactDOM.render(
<div>
<h2>DEMO</h2>
<SomeComponent data={rawData}/>
</div>,
document.getElementById('reactdiv')
);
.item {
border: 2px solid black;
margin-bottom: 10px;
padding: 2px 15px;
cursor: default;
width: fit-content;
}
.unselected { background-color: #EEEEFF; }
.selected { background-color: #6666AA; color: white}
<div id='reactdiv'/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
The clicked needs to be a data-structure that can track which of the items rendered are clicked (ie, selected) and which are not.
In this snippet, it is set as an object
For simplicity of the demo, the same clicked object serves a secondary purpose of holding the total price.
When user clicks on any item, it's background color changes (using getClass method)
And, the price of the item is added to or removed from total
Overall - this is a fairly simple, straight-forward code snippet.
As per my understanding of the question what you can do is, add another state called divIndex and change the condition to clicked && divIndex === index or you can just remove the clicked state and only use the divIndex state also like divIndex === index.
so i arrived to some kind of solution but it's not perfect, in my newOption, it does not remove the element i clicked on sometimes... it works perfect to add an element in the tab though...
here what i did.
const [selectedOption, setSelectedOption] = useState([]);
const [isSelected, setIsSelected] = useState(false);
const handleConfigClick = (charges, index) => {
const newOption = [...selectedOption];
// Est-ce que l'option est déjà présente ?
// console.log("index", index);
const exist = newOption.find((elem) => elem.id === charges.id);
// console.log("L'élément trouvé ====> ", exist);
if (exist) {
newOption.splice(index, 1);
setIsSelected(false);
console.log("exist");
} else {
console.log("existe pas");
setIsSelected(true);
newOption.push(charges);
}
console.log("newoption", newOption);
setSelectedOption(newOption);
};
it's probably not opti but it's close to my result, just need to resolve the case when the option does not leave the tab on click

React dropdown only show value if clicked on specific data using e.target?

So I'm trying to make this accordion and right now it maps through my data and essentially shows all the dropdown values when I click on my h1, but I only want it to show the dropdown for the specific h1 I clicked and not display the entire values
const Accordion = () => {
const [clicked, setClicked] = useState(false);
const toggle = e => {
if (e.target) {
setClicked(!clicked);
}
console.log(e.target);
};
return (
<div>
{Data.map((item, index) => {
return (
<div key={index}>
<h1 onClick={e => toggle(e)}>{item.question}</h1>
{clicked ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
</div>
);
})}
</div>
);
};
My toggle function shows the correct e.target value of the h1 I clicked on, but I don't know how to only display that h1 value when I click on it instead of showing all h1's values
Here is my data file
export const Data = [
{
question: 'What 1?',
answer: 'answer 1'
},
{
question: 'What 2',
answer: 'answer 2'
},
{
question: 'What 3?',
answer: 'answer 3'
}
];
How would I refactor my code to only show question 1 and answer 1, vs showing all questions and answers on click?
You need to track which div has been clicked . You can do that assigning id in your data like this:
const Data = [
{
question: "What 1?",
answer: "answer 1",
id: 0
},
{
question: "What 2",
answer: "answer 2",
id: 1
},
{
question: "What 3?",
answer: "answer 3",
id: 2
}
];
Then send this id on toggle method and check if id is equal to selected one or not like this:
const toggle = (e, id) => {
if (e.target) {
setSelected(id === selected ? null : id); //selected and id are same it means close the toggle
}
console.log(e.target);
};
and inside render check like this:
{selected === item.id ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
Here is full demo and code: https://codesandbox.io/s/toggle-accordian-eplpw
My suggestion would be to a separate component with its own state and use that for each accordion item.
const AccordionItem = (props) => {
const [clicked, setClicked] = useState(false);
return (
<div>
<h1 onClick={() => setClicked(! clicked)}>{props.question}</h1>
{clicked && (
<div>
<p>{item.answer}</p>
</div>
)}
</div>
);
};
const Accordion = () => {
return (
<div>
{Data.map((item, index) => {
return (
<AccordionItem
key={index}
question={item.question}
answer={item.answer}
/>
);
})}
</div>
);
};
You have to put the clicked question in state rather than just whether something was clicked or not. E.g. https://codesandbox.io/s/summer-night-4uulw?file=/src/App.js
const Data = [
{
question: 'What 1?',
answer: 'answer 1',
},
{
question: 'What 2',
answer: 'answer 2',
},
{
question: 'What 3?',
answer: 'answer 3',
},
];
const Accordion = () => {
const [activeQn, setActiveQn] = useState(null);
const toggle = ( index ) => {
// If the clicked qn is already active, then collapse it
if ( activeQn === index ) {
return setActiveQn(null)
}
// Otherwise show the answer to the clicked qn
setActiveQn(index)
};
return (
<div>
{Data.map((item, index) => {
return (
<div key={index}>
<h1 onClick={() => toggle(index)}>{item.question}</h1>
{activeQn === index ? (
<div>
<p>{item.answer}</p>
</div>
) : null}
</div>
);
})}
</div>
);
};
That way, only the clicked answer will show and if the user wishes to collapse the answer, that can also be done.

Push specific object into list using id React

I got a list of objects which im displaying on the screen with the help of .map function.
It looks like this:
Component 1:
let itemList = [
{
type: "White T-shirt",
id: 1,
cost: 300,
image: whiteTshirt
},
{
type: "Purple T-shirt",
id: 2,
cost: 350,
image: purpleTshirt
},
{
type: "Baseballcap",
id: 3,
cost: 150,
image: whiteCap
},
{
type: "Vice Golfball",
id: 4,
cost: 40,
image: golfball
},
{
type: "Mousepad",
id: 5,
cost: 200,
image: mousepad
}
];
let products = itemList.map(items => {
let item =
<div key={items.id}>
<h2>{items.type}</h2>
<img className="image" src={items.image}></img>
<p className="price">${items.cost}</p>
<button onClick={onBuy} className="buy-btn">Buy</button>
</div>
return item;
})
return(
{shoppingcart ? <Component2 /> : null}
<main> {products} </main>
)
Component 2:
const Comopnent2 = props => {
const [webshop, setWebshop] = useState(false);
return(
<div>
{webshop ? <Webshop /> : null }
<a href="/Webshop" onClick={e => { e.preventDefault(); setWebshop(true)}} >
<p className="to-shop"> Back to shop</p></a>
<h2 className="shopping-header">Your Shopping Cart</h2>
<div className="cart-container">
// Here i want to object that i clicked display
</div>
)
}
What i want is to push one specific object to another array in another component that i have. I want to do that when i click the button which calls the onBuy funcion. How do i manage that? Thanks.
First create a hook for the cart item inside component 1:
const [cartItem, setCartItem] = useState();
set cartItem whenever the button is clicked and onBuy is called:
onBuy (id) {
let checkoutItem = this.itemList.find(item => item.id === id)
setCartItem(checkoutItem)
}
You'll be required to pass item id when you declare the button which call onBuy function.
let products = itemList.map(items => {
let item =
<div key={items.id}>
<h2>{items.type}</h2>
<img className="image" src={items.image}></img>
<p className="price">${items.cost}</p>
<button onClick={onBuy(item.id)} className="buy-btn">Buy</button>
</div>
return item;
})
To pass this selection to component 2. You can pass it as a prop in component 1:
return(
{shoppingcart ? <Component2 item={cartItem} /> : null}
<main> {products} </main>
In the component 2 You can display the data accordingly from props:
const Comopnent2 = props => {
const [webshop, setWebshop] = useState(false);
return(
<div>
{webshop ? <Webshop /> : null }
<a href="/Webshop" onClick={e => { e.preventDefault(); setWebshop(true)}} >
<p className="to-shop"> Back to shop</p></a>
<h2 className="shopping-header">Your Shopping Cart</h2>
<div className="cart-container">
{prop.item.name} //whatever properties your cart item has, I have used name just for example
</div>
)
}

React hook page throws 'filter' of undefined (anonymous) exception and unable to perform search

I have implemented a React hookhome page where I have got list of players displaying, I have added an input search to search the players based on the player name or Position, but it throws below exception,could someone please advise the cause of exception;
TypeError: Cannot read property 'filter' of undefined (anonymous
function) C:/Project1/soccerpep/src/components/Home.js:18 15 |
setSearchTerm(event.target.value); 16 | }; 17 | React.useEffect(()
=> {
18 | const results = playerList.name.filter(player =>
| ^ 19 | player.toLowerCase().includes(searchTerm) 20 | ); 21 | setSearchResults(results);
The react hook page follows below:
import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
var playerList = [
{ image: '/images/person.png', name: 'Player 1', position: "Forward" },
{ image: '/images/person.png', name: 'Player 2', position: "Defense" },
{ image: '/images/person.png', name: 'Player 3', position: "Mid Fielder" },
{ image: '/images/person.png', name: 'Player 4', position: "Forward" }
];
const Home = () => {
const [searchTerm, setSearchTerm] = React.useState("");
const [searchResults, setSearchResults] = React.useState([]);
const handleChange = event => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const results = playerList.name.filter(player =>
player.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}, [searchTerm]);
return (
<div className="App">
<div className="wrapper">
<div className="playerList_header">
<h1>Players</h1>
<label>
<div className="playerSearch_Home">
<div className="playerSearch_Icon">
<img src="/images/search-image-player.jpg"></img>
</div>
<input type="text" className="playerSearch_Home_Input" placeholder="Search players..." value={searchTerm} onChange={handleChange}/>
</div>
</label>
</div>
<div className="playerList_home_page">
<div className="grid-container">
{
playerList.map(player => {
return (
<div className="grid-item">
<div>
<img className="playerProfilePic_home_tile" key={player.image} src={player.image}></img>
</div>
<div className="playerProfile_grid_border">
<h3 key={player.name}>{player.name}</h3>
<span className="playerPosition_home_tile" key={player.position}>{player.position}</span>
</div>
</div>
);
})
},
{
searchResults.map(player => {
return (
<div className="grid-item">
<div>
<img className="playerProfilePic_home_tile" key={player.image} src={player.image}></img>
</div>
<div className="playerProfile_grid_border">
<h3 key={player.name}>{player.name}</h3>
<span className="playerPosition_home_tile" key={player.position}>{player.position}</span>
</div>
</div>
);
})
}
</div>
</div>
</div>
</div>
);
}
export default Home;
playerList is an array, not an object, it doesn't have property name:
const results = playerList.filter(player =>
player.name.toLowerCase().includes(searchTerm) || player.position.toLowerCase().includes(searchTerm)
);
.filter() can be used only on arrays, you tried to run on name property which does not exist.
Instead you can use as the following:
var playerList = [
{ image: '/images/person.png', name: 'Player 1', position: "Forward" },
{ image: '/images/person.png', name: 'Player 2', position: "Defense" },
{ image: '/images/person.png', name: 'Player 3', position: "Mid Fielder" },
{ image: '/images/person.png', name: 'Player 4', position: "Forward" }
];
const searchTerm = 'Player 2';
const results = playerList.filter(p => p.name.toLowerCase() === searchTerm.toLowerCase());
console.log(results);
I hope this explains!

Categories