React, onMouseEnter event trigger all items instead of one hovered? - javascript

I have two cards now when I hover on one card it triggers in all two cards on hover (onMouseEnter)
Here is the solutions I have
import React, { useState } from "react";
const Buttons = () => {
const [isShown, setIsShown] = useState(false);
return (
<div>
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="wrapper-btn"
>
{isShown && <button> test 1 </button>}
</div>
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="wrapper-btn"
>
{isShown && <button> test 2 </button>}
</div>
</div>
);
};
export default Buttons;
What is wrong here?

both share the same state, you can abstract your code to another component where each one has an independent state:
import React, { useState } from "react";
const Buttons = () => {
return (
<div>
<ButtonDisplay btnContent='test 1' />
<ButtonDisplay btnContent='test 2' />
</div>
);
};
export default Buttons;
const ButtonDisplay = ({ btnContent }) => {
const [isShown, setIsShown] = useState(false);
return (
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="wrapper-btn"
>
{isShown && <button> { btnContent } </button>}
</div>
)}
this would be the approach I would take since keeps your code dry.
other approach possible would change isShown state to an array that tracks each button isShown state, where onMouseEnter|Leave would update a specific index of that array and also read from that one, hence you would render your button based on specific value from an index of your state. or you could create a state to each button which would be the least optimal when you have multiple buttons.

Related

Change styles on hover of an appropriate item

How can I Change styles on hover of an appropriate item react? Now the styles are changed of all of the items at a time. Hovering on add to cart button I need to change only the chosen div styles.
https://codesandbox.io/s/trusting-moon-djocul?file=/src/components/Category.js**
import React, { useState } from "react";
import styles from "./category.module.css";
import Categories from "./Categories";
const Category = () => {
const [hovered, setHovered] = useState(false);
const [data, setData] = useState(Categories);
const style = hovered
? { backgroundColor: "#ffcbcb", color: "#fff", transition: "all 0.5s ease" }
: {};
const filterResult = (catItem) => {
const result = Categories.filter((curData) => curData.category === catItem);
setData(result);
};
return (
<>
<div className={styles.items}>
{data.map((value) => {
const { id, title, price, description } = value;
return (
<>
<div className={styles.item} key={id} style={style}>
<h1>{title}</h1>
<p>
{price} <span>$</span>
</p>
<p>{description}</p>
<button
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
className={styles.btn}
>
Add to Cart
</button>
</div>
</>
);
};
export default Category;
That's because you have a single "hovered" state shared between all your cards, you should create a "Card" component and have that component have its own hovered state
return (
<>
<div className={styles.items}>
{data.map((value) => {
return (
<>
<Card {...props} />
</>
);
Problem
This issue is occurring cause you are applying CSS to all the cards. The only small thing you are missing in your logic is adding CSS to the only card whose button was being hovered.
Solution
That can be achieved by using event objects on mouse enter and mouse leave events.
<div className={styles.item} key={id} style={style}>
<h1>{title}</h1>
<p>
{price} <span>$</span>
</p>
<p>{description}</p>
<button
onMouseEnter={(e) =>
e.currentTarget.parentElement.classList.add("active_card")
}
onMouseLeave={(e) =>
e.currentTarget.parentElement.classList.remove("active_card")
}
className={styles.btn}
>
Add to Cart
</button>
</div>
This will add a class of "active_card" on the card whose Add To Card button is being hovered, then you can apply your desired styling to that class.
.active_card {
background-color: #ffcbcb !important;
}
Example
Working Code Sandbox Example

In react, how to pass states down as props?

This is a very noob question but I've been trying all day do implement this. Please help me out.
Sorry for the length, just tried to put out the whole thing I am struggling with
I am trying to build custom buttons and to do so, I created a component so I can create as many buttons that I want. For that I declared a state and passed down some information as props, which is as follows:
import React, {useState} from 'react'
import Button from '../components/Button'
function CustomButton() {
const [clicked, setClicked] = useState(false)
return (
<div className='CustomButton'>
<Navbar />
<Button setClicked={setClicked} name="Button One" clicked={clicked}/>
<Button setClicked={setClicked} name="Button Two" clicked={clicked}/>
<Button setClicked={setClicked} name="Button Three" clicked={clicked}/>
</div>
)
}
export default CustomButton
As you can see, we passed the state and name of that button down. To render this Buttons, following component has been created:
import React from 'react'
import Modal from './Modal/Modal'
function Button({setClicked, name, clicked}) {
return (
<div>
<button onClick={() => {setClicked(true)}}>{name}</button>
{clicked && <Modal closeModal={setClicked} name={`You Clicked ${name}`} />}
</div>
)
}
export default Button
And lastly, when once a button is clicked, we want to perform some action. That action is to pop the Modal on a screen. And to do so, we created a Modal and passed down few props. Code for the same is as follows:
import React from 'react'
function Modal({closeModal, name}) {
return (
<div className='modal'>
<div className='modalContainer'>
<p>{name}</p>
<div>
<button onClick={() => {closeModal(false)}}>×</button>
</div>
</div>
</div>
)
}
export default Modal
The expected result is for a Modal to pop with "You clicked button One", supposing we clicked one something similar to this.
The actual result is that all three Modals pop up one above the other when any of the three buttons are passed. The result:
I realize that I am passing the states wrong way. When any of the button is clicked all three get set to true. I simply don't realize how. Don't they create a method for each one?
Also, can you guys please teach me a better/understandable way to write clicked logic. Like maybe
if(clicked){
<Modal closeModal={setClicked} name={`You Clicked ${name}`} />
}
Because you bind all three buttons with one state, You need a state as array, with items equal to the number of buttons.
const [clicked, setClicked] = useState([false, false, false])
return (
<div className='CustomButton'>
<Navbar />
{
clicked.map((button, i) => {
return <Button setClicked={setClicked} name="Button Three" clicked={clicked[i]} index={i}/>
})
}
</div>
)
Then in the button component.
function Button({setClicked, name, clicked, index}) {
return (
<div>
<button onClick={() => {setClicked(prev => prev.map((item, i) => {
return i === index ? true : item
}))}}>{name}</button>
{clicked && <Modal closeModal={setClicked} name={`You Clicked ${name}`} />}
</div>
)
}
And the modal component.
function Modal({ closeModal, name, index }) {
return (
<div className="modal">
<div className="modalContainer">
<p>{name}</p>
<div>
<button
onClick={() => {
closeModal((prev) =>
prev.map((item, i) => {
return i === index ? false : item;
})
);
}}
>
×
</button>
</div>
</div>
</div>
);
}
You can find a working example on this link.
https://codesandbox.io/s/old-wood-zgjno9
You can implement multiple modals like this:
import { useState } from "react";
export default function App() {
const [showModal1, setShowModal1] = useState(false);
const [showModal2, setShowModal2] = useState(false);
return (
<div className="App">
<button onClick={(e) => setShowModal1(true)}>Button 1</button>
<button onClick={(e) => setShowModal2(true)}>Button 2</button>
{showModal1 && (
<Modal text="Modal 1" onClose={(e) => setShowModal1(false)} />
)}
{showModal2 && (
<Modal text="Modal 2" onClose={(e) => setShowModal2(false)} />
)}
</div>
);
}
const Modal = ({ text, onClose }) => {
return (
<div>
{text}
<button onClick={onClose}>Close</button>
</div>
);
};
Working example

How to iterate through select dropdown in react using props passed from Parent Component?

I am using props from a parent component in a modal and I am unsure of how to make it show in a select dropdown dynamically. I see that the stubbed hardcoded 'Agent' value is being repeated but how do I actually make that dynamically show the values from the props I am receiving? What am I missing here?
React Modal Component
import React, { useState, useEffect } from 'react'
import Button from '#/components/Button.jsx'
import Loading from '#/components/Loading.jsx'
import Modal from '#/components/Modal.jsx'
/**
* Handles displaying the capabilities modal for creating new capabilities or updating exiting ones.
* #component
*/
export default function AddCapabilitiesModal({ profile, onClose }) {
const [error, setError] = useState(null)
const [loading, setLoading] = useState(false)
const [agents, setAgents] = useState([])
const [profiles, setProfiles] = useState([])
useEffect(() => {
if (profile) {
console.log(profile);
setAgents(profile.map((f) => ({ label: f.agent, value: f.agent })))
setLoading(false);
}
}, [])
/**
* Erases information of text field variables and closes modal.
*/
const closeModal = () => {
setError(false)
setLoading(false)
onClose()
}
const dummyClick = () => {
console.log('click')
}
return (
<div>
<Modal>
{error && (
<label className='high'>
Error: Please confirm inputs and try again.
</label>
)}
<div className='modal-title'>
<label>EDIT STATUS</label>
</div>
<br />
<Loading text='Saving Test...' visible={loading} />
{!loading && (
<div id='addCapSelectInput' className='sub-filters'>
<div className='lrow filter-options group light-grey-bg'>
<select>
{agents.map((agent) => (
<option key={agent} value={agent}>
Agent
</option>
))}
</select>
</div>
</div>
)}
<div className='modal-row top-horizontal-divider'>
<Button onClick={closeModal} className='modal-cancel'>
Cancel
</Button>
<Button
onClick={dummyClick}
className='modal-submit'
>
Submit
</Button>
</div>
</Modal>
</div>
)
}
As I can see your agent in state is a list of objects containing label and value keys. So you should just rewrite render part to something like this:
{
agents.map((agent) => (
<option key={agent.label} value={agent.value}>
{agent.label}
</option>
))
}

How do you remove a CSS class from a certain element of a list with React

I'm trying to remove a CSS class from a specific item when clicking on that item's button. Removing the CSS class will make a menu appear. How would I go about doing this with React? Here's the code.
import "./Homepage.css"
import React, { useState, useEffect, useRef } from "react"
// import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
// import { faArrowDown } from "#fortawesome/free-solid-svg-icons"
import { Link } from "react-router-dom"
import useFetch from "./useFetch"
import Axios from "axios"
export default function Homepage() {
const [body, setBody] = useState("")
const [sortedData, setSortedData] = useState("")
const [data, setData] = useState("")
const [css, setCss] = useState("")
const [flash, setFlash] = useState(null)
const posts = useFetch("http://localhost:5000/api/data")
const firstRender = useRef(true)
useEffect(() => {
let test = JSON.parse(window.localStorage.getItem("user"))
console.log(test)
setData(posts)
}, [posts])
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
return
}
data.sort(function (a, b) {
return new Date(b.date) - new Date(a.date)
})
setSortedData(data)
}, [data])
const handleSubmit = (e) => {
e.preventDefault()
Axios.post("http://localhost:5000/api/react-create-post", { text: body }, { withCredentials: true })
.then((res) => {
setSortedData((prevArray) => [res.data.post, ...prevArray])
setFlash("Successfully created post.")
setCss("success-msg")
setBody("")
})
.catch((err) => {
setCss("error-msg")
setFlash("Field cannot be left blank.")
})
}
const handleClick = (e) => {
e.preventDefault()
e.target.parentElement.children[1]
}
return (
<div>
<center>
<div className="create-container">
<div className="posts-title">Create Post</div>
<form id="theForm" onSubmit={(e) => handleSubmit(e)}>
<textarea onChange={(e) => setBody(e.target.value)} value={`${body}`} id="theInput" className="post-input" name="text" type="text"></textarea>
<button className="submit-btn">POST</button>
</form>
</div>
<div id="postsContainer" className="posts-container">
<div className="posts-title">Latest Posts</div>
{flash ? <div className={css}>{flash}</div> : console.log()}
<div id="postInput">
{sortedData &&
sortedData.map((item) => {
return (
<div className="post-container" key={item._id}>
<Link className="a" to={`/user/${item.author}`}>
<h3 className="author">{item.author}</h3>
</Link>
<div className="date">{item.date.toLocaleString()}</div>
<div className="options-cont">
<button onClick={(e) => handleClick(e)} id="optionsBtn" className="options-btn">
<i className="fas fa-ellipsis-v"></i>
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} data-text={`${item.body}`} id="editBtn" className="edit inside-btn invisible">
Edit
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} id="deleteBtn" className="delete inside-btn invisible">
Delete
</button>
<br></br>
</div>
<p className="body-text">{item.body}</p>
</div>
)
})}
</div>
</div>
</center>
</div>
)
}
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
As pilchard said, you probably want to make each of those its own component with its own "showing" state, or at least "showing" prop.
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
That would be true if you used a single flag in state. But instead, use a set of flags, one flag for each menu, perhaps keyed by item._id.
Assuming you don't do the refactoring pilchard (and I) suggest:
You haven't shown us enough code for me to know whether you're using class components or function components, so I'm going to guess function components with hooks. If so, the initial state would be:
const [showing, setShowing] = useState(new Set());
Then when rendering, you'd assign the class:
<theElement className={showing.has(item._id) ? "class-if-any-to-show-it" : "class-if-any-to-not-show-it" ...
To toggle, in the button pass the ID:
<button onClick={(e) => handleClick(e, item._id)}
and then update state as appropriate:
const handleClick = (e, id) => {
e.preventDefault()
setShowing(showing => {
showing = new Set(showing);
if (showing.has(id)) {
showing.delete(id);
} else {
showing.add(id);
}
return showing;
});
};

Share State between two specific instances of the same react component React

Before y'all say global state(redux), I'd like to say one thing. I'm mapping through an array I fetched from my API. I receive images and map over them and render my Slider component. Every 2 sliders must share the same state. So, then if i move to the next slide in the first slider, then the second slider must also go to the next slide(but not any other slides). If I move to the next slide in the 5th slider, the 6th must also move to the next slide... so on.
Component where I map over slides:
<div className='image-grid'>
{screenshots.map((imagesByResolution, resIdx, screenshotResArr) => {
return imagesByResolution.map((img, scriptIdx, screenshotScriptsArr) => {
return <Slider slides={formattedSlides} />;
});
})}
</div>
Slider:
import Button from '#material-ui/core/Button';
import MobileStepper from '#material-ui/core/MobileStepper';
import { useTheme } from '#material-ui/core/styles';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
import { encodeImage } from '../services/images';
import useStyles from '../styles/slider';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export interface ISlide {
title: string;
img: ArrayBuffer;
}
interface Props {
slides: ISlide[];
}
export default function Slider(props: Props) {
console.log(props);
const { slides } = props;
const classes = useStyles();
const theme = useTheme();
const [activeSlide, setActiveSlide] = React.useState(0);
const maxSlides = slides.length;
const handleNext = () => {
setActiveSlide((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveSlide((prevActiveStep) => prevActiveStep - 1);
};
const handleSlideChange = (step: number) => {
setActiveSlide(step);
};
return (
<div className={classes.root}>
<div className={classes.header}>
<h4 className={classes.title}>{slides[activeSlide].title}</h4>
</div>
<AutoPlaySwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeSlide}
onChangeIndex={handleSlideChange}
enableMouseEvents
>
{slides.map((slide, index) => (
<div key={index}>
{Math.abs(activeSlide - index) <= 2 ? (
<img className={classes.img} src={encodeImage(slide.img, 'image/png')} alt={slide.title} />
) : null}
</div>
))}
</AutoPlaySwipeableViews>
<MobileStepper
steps={maxSlides}
position='static'
variant='text'
activeStep={activeSlide}
nextButton={
<Button size='small' onClick={handleNext} disabled={activeSlide === maxSlides - 1}>
Next
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
}
backButton={
<Button size='small' onClick={handleBack} disabled={activeSlide === 0}>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
}
/>
</div>
);
}
If this is not possible using either some global state management library or plain ol' react state, what is the other alternative? Thanks in advance!
Pass a unique key prop to each instance of your component.
Credits: https://stackoverflow.com/a/65654818/9990676

Categories