How to dynamically change className of a component in React? - javascript

I want to make a button check which add a new calssName to my list. I use a function to update a state and take the string. If you want to help me be more specific because I am a beginner. Thanks !
const [check, setCheck] = useState({
id: '',
status: false
});
This is the function. With 'on' I take the string to add to id.
let complet = (on) =>{
if(check.status == false){
setCheck({
id: on,
status: true
})
}
else {
setCheck({
id: on,
status: false
})
}
}
And how I Display the list and how I check correspond to the string.
return(
<div className='display'>
{ list.map( (list,index) => (
<div className={ check.status && check.id == list ? 'lista complet' : 'lista'} key= {index} id='lista' >
{list}
<button className='btnCheck' onClick={complet.bind(this, list)}> <FcCheckmark/> </button>
<button className='btnRemove' onClick={remove.bind(null, list)}> <BsTrash/> </button>
</div>
))}
</div>
)

If you want to store the checked ids and the unchecked ids, you must change your state variable because currently it can only stores a single element. However, it seems you are rendering a list of elements that can be checked individually
Here is a possible solution :
function App({list}) {
const [checkIds, setCheckIds] = useState(() => {
const item = localStorage.getItem('checkIds');
return item ? JSON.parse(item) : {};
});
// reset the checkIds when the list is updated
useEffect(() => setCheckIds({}), [list]);
// save the checkIds into the local storage
useEffect(() => {
localStorage.setItem('checkIds', JSON.stringify(checkIds));
}, [checkIds]);
function checkId(id) {
setCheckIds({...checkIds, [id]: true);
}
function uncheckId(id) {
setCheckIds({...checkIds, [id]: false);
}
return (
<div className='display'>
{list.map(id => (
<div key={id} id={id} className={check[id] ? 'lista complet' : 'lista'}>
{id}
<button className='btnCheck' onClick={() => checkId(id)}>
<FcCheckmark/>
</button>
<button className='btnRemove' onClick={() => uncheckId(id)}>
<BsTrash/>
</button>
</div>
))}
</div>
)
}

Related

Creating like button for multiple items

I am new to React and trying to learn more by creating projects. I made an API call to display some images to the page and I would like to create a like button/icon for each image that changes to red when clicked. However, when I click one button all of the icons change to red. I believe this may be related to the way I have set up my state, but can't seem to figure out how to target each item individually. Any insight would be much appreciated.
`
//store api data
const [eventsData, setEventsData] = useState([]);
//state for like button
const [isLiked, setIsLiked] = useState(false);
useEffect(() => {
axios({
url: "https://app.ticketmaster.com/discovery/v2/events",
params: {
city: userInput,
countryCode: "ca",
},
})
.then((response) => {
setEventsData(response.data._embedded.events);
})
.catch((error) => {
console.log(error)
});
});
//here i've tried to filter and target each item and when i
console.log(event) it does render the clicked item, however all the icons
change to red at the same time
const handleLikeEvent = (id) => {
eventsData.filter((event) => {
if (event.id === id) {
setIsLiked(!isLiked);
}
});
};
return (
{eventsData.map((event) => {
return (
<div key={event.id}>
<img src={event.images[0].url} alt={event.name}></img>
<FontAwesomeIcon
icon={faHeart}
className={isLiked ? "redIcon" : "regularIcon"}
onClick={() => handleLikeEvent(event.id)}
/>
</div>
)
`
Store likes as array of ids
const [eventsData, setEventsData] = useState([]);
const [likes, setLikes] = useState([]);
const handleLikeEvent = (id) => {
setLikes(likes.concat(id));
};
return (
<>
{eventsData.map((event) => {
return (
<div key={event.id}>
<img src={event.images[0].url} alt={event.name}></img>
<FontAwesomeIcon
icon={faHeart}
className={likes.includes(event.id) ? "redIcon" : "regularIcon"}
onClick={() => handleLikeEvent(event.id)}
/>
</div>
);
})}
</>
);
Your issue is with your state, isLiked is just a boolean true or false, it has no way to tell the difference between button 1, or button 2 and so on, so you need a way to change the css property for an individual button, you can find one such implementation by looking Siva's answer, where you store their ids in an array

How to change cart button "ADD" to some other name

I want to make the "ADD" button to " ADDED" after I clicked the ADD button and make it disabled.
{
products.map((val, i)=>(
<div className="product_item">
{
val.productType === 'chicken'?
<>
<img src={img2}></img>
<p className="product_data">Chicken Name: {val.chickeName}</p>
<p align="left" className="product_data">weight: 500gm</p>
<p align="left"className="product_data">₹{val.price}/- <button onClick={addTocartProduct}>ADD</button></p>
</>: null
}
</div>
))
}
I have sample project in react js having add to cart button is there. But I want to make the "ADD" button to " ADDED" after I clicked the ADD button and make it disabled.
If anyone can know please, drop your answer.
Considering that you have an array of products you should initialize an array of bool:
const [buttonsClicked, setButtonsClicked] = useState(new Array(products).fill(false));
Then
{
products.map((val, i)=>(
<div className="product_item">
{
val.productType === 'chicken'?
<>
<img src={img2}></img>
<p className="product_data">Chicken Name: {val.chickeName}</p>
<p align="left" className="product_data">weight: 500gm</p>
<p align="left"className="product_data">₹{val.price}/- <button onClick={(e) => addTocartProduct(val, i)}>{buttonsClicked[i] ? 'ADDED' : 'ADD'}</button></p>
</>: null
}
</div>
))
}
And finally the addTocartProduct function:
const addTocartProduct = (val , i) => {
let result = [...buttonsClicked];
result[i] = !result[i];
setButtonsClicked(result);
}
Youjust need a simple ternary condition and a state. Here is an example :
const App = () => {
const [isAdded, setIsAdded] = React.useState(false);
const handleClick = () => {
setIsAdded(true);
}
return (
<div>
<button onClick={handleClick}>{isAdded ? 'ADDED' : 'ADD'}</button>
</div>
);
};
You have to define some state in your component
const [ctaButton,setCtaButton] = useState('ADD')
Then in your addTocartProduct function set the state to new one
setCtaButton('ADDED')
Finally
<button onClick={addTocartProduct}>{ctaButton}</button>

Render a button (when hovered) for only one element of an array, when mapping an array

const Chat = () => {
const bodyRef = useRef(null)
const [{messages,roomMessages,roomID,socket,user}, dispatch] = useStateValue()
const [dropdown, setDropdown] = useState(false)
useEffect(()=>{
const setScreen = () =>{
bodyRef.current.scrollTop = bodyRef.current.scrollHeight
}
setScreen()
},[])
const updateMessages = (message) =>{
const allMessages = messages
allMessages.push(message)
dispatch({
type: "SET_MESSAGES",
item: allMessages
})
bodyRef.current.scrollTop = bodyRef.current.scrollHeight
}
useEffect(() => {
socket.on('new-message', (message) =>{
if(messages === null){
console.log('here it was null')
} else {
updateMessages(message)
}
})
// clean up socket on unmount
}, [])
const handleDropdown = (index) =>{
setDropdown(true)
console.log(index)
if(dropdown === true){
setDropdown(false)
}
}
return (
<div className={'chat'}>
<ChatHeader/>
<div ref={bodyRef} className={'chat__body'}>
{messages?.map((item,index)=>(
<Emojify key={item._id}>
<div
onMouseEnter={() => handleDropdown(index)}
onMouseLeave={handleDropdown}
className={`chat__message ${item.name === user.displayName && "chat__reciever"}`}>
<h5 className={'chat__messageUser'}>
{item.name}
</h5>
<p className={'chat__messagetext'}>
{item.message}
</p>
{dropdown && <Button><ArrowDropDownIcon/></Button>}
{item.img && <img alt={''} className={'chat__img'} src={item.imgURL}/>}
</div>
</Emojify>
))}
</div>
<div className={'chat__footer'}>
<ChatFooter/>
</div>
</div>
);
I want to render the Button {dropdown && <Button><ArrowDropDownIcon/></Button>} when it is hovered over.
Right now if I hover over one div it gets rendered for all other mapped divs but I don't want this; it shoul be for a specific div only.
How would I do this? Does someone have an idea? I passed index but can't seem to use it as I desire.
Instead of storing a single "global" boolean for dropdown open or not (which all mapped elements are reading), you could correlate a specific dropdown you want to be open. It's good you are already passed an index to the handler.
Change state to store null (no dropdown) or an index (show dropdown).
const [dropdown, setDropdown] = useState(null);
Update the handler to store/toggle an index.
const handleDropdown = (index) =>{
setDropDown(value => value === index ? null : index);
}
In the render simply check if the current index matches the dropdown state.
<div
onMouseEnter={() => handleDropdown(index)}
onMouseLeave={() => handleDropdown(index)} // <-- handle index in mouse leave
className={`chat__message ${
item.name === user.displayName && "chat__reciever"
}`}
>
<h5 className={"chat__messageUser"}>{item.name}</h5>
<p className={"chat__messagetext"}>{item.message}</p>
{dropdown === index && ( // <-- check index match with dropdown value
<Button>
<ArrowDropDownIcon />
</Button>
)}
{item.img && (
<img alt={""} className={"chat__img"} src={item.imgURL} />
)}
</div>

Controlled Input onChange Event Fires Only Once - React

Only one key press is registered, then the input loses focus however I can click back into the component and add ... one character. State is updated.
Since the child components are state-less I assumed that just passing the handlers down as props as described in the docs would be sufficient, but everything from changing the binding for the method to adding a constructor have yielded the same results. I can usually find an answer on the site but no luck this time.
const list = [
{
id : 0,
title : "Went well",
showCard : false,
cards : [],
color : "pink"
},
{
id : 1,
title : "To Improve",
showCard : false,
cards : [],
color : "yellow"
},
{
id : 2,
title : "Action Items",
showCard : false,
cards : [],
color : "blue"
}
]
class App extends Component {
state = {list : list, usrInpt : ""}
handleChange = e => {this.setState({usrInpt:e.target.value})}
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
this.setState({list:updatedList})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}
render() {
return (
<div className="App">
<h2>Metro Board</h2>
<ul className="container">
{this.state.list.map((item) =>
<List key={(Math.random() * 1)} text={item.title}
id={item.id} cards={item.cards} color={item.color}
usrInpt={this.state.usrInpt} add={this.add} handleChange={this.handleChange}/>
)}
</ul>
</div>
)
}
}
const List = (props) =>{
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <ListItem key={(Math.random() * 1)}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
}
const ListItem = (props) =>{
console.log("card props and value",props)
return <li>
<div className="card" style={{backgroundColor: props.color}}>
<textarea type="text"
className="card"
placeholder="Enter text here"
value={props.usrInpt}
onChange={e => props.handleChange(e)}>
</textarea>
<div><a className="ltCtl" href="./logo" onClick={e => console.log(e)}><</a>
<a className="clCtl" href="./logo" onClick={e => console.log(e)}>x</a>
<a className="rtCtl" href="./logo" onClick={e => console.log(e)}>></a>
</div>
</div>
</li>
}
Both List && ListItem are separate files... Any help would be great. Thanks.
UPDATE:
I was able to reach out to a full time developer and it seems I screwed up by trying to make unique keys :
The key needs to be consistent, but in this case it is a different value every time
React uses the key when it IDs which element is focusing on, but in this case, it is different than the last render. So React does not know what to focus on. You can have unique keys if you use a string with the index of the loop in it, or if you use an ID that you store outside in the loop, like in state
It does need to be unique, but it also needs to be consistent.
So the code:
return (
<Card
key={Math.random() * 1} // <--- Don't!!
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}
/>
);
was causing React to lose track of what to render since the keys where changing with each render. The preferred method is using a string interpolation with an identifier and an index if a loop is used:
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <Card key={`card-column-${props.id}-${i}`}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
Which was also a comment made by #miyu ... which I did not test. Listen to your peers and mentors... you will not lose 12 hours chasing bugs. Thanks.
state is non-hierarchical. Meaning, when you update a child object of your state but the parent object is not updated, then react will not trigger componentDidChange.
Try adding a counter which gets updated when the list is updated.
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
let counter = this.state.counter || 0;
this.setState({list:updatedList, counter: counter++})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}

Pass index as state to component using React

I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910

Categories