Change styles on hover of an appropriate item - javascript

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

Related

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

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

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.

how to toggle between two css classes view types with react

I have a List and Grid type display. How do I toggle between them in React. I want to toggle between jsGridView and jsListView classes.
This is the vanilla js of the toggling of classes
const listView = document.querySelector('.list-view');
const gridView = document.querySelector('.grid-view');
const projectsList = document.querySelector('.project-boxes');
listView.addEventListener('click', () => {
gridView.classList.remove('active');
listView.classList.add('active');
projectsList.classList.remove('jsGridView');
projectsList.classList.add('jsListView');
});
gridView.addEventListener('click', () => {
gridView.classList.add('active');
listView.classList.remove('active');
projectsList.classList.remove('jsListView');
projectsList.classList.add('jsGridView');
});
** this is my react file where I have the display items and buttons to toggle. how do I implement the toggle event listeners into my react file**
How do I toggle between the two classes - jsGridVew and jsListView
const [isActive, setIsActive] = useState(false)
const listToggle = () => {
setIsActive(!isActive)
}
<button key={isActive} className="view-btn list-view" title="List View" onClick={listToggle}>
<i className="fal fa-list-ul fa-2x"></i>
</button>
<button className="view-btn grid-view active" title="Grid View">
<i className="fal fa-th-large fa-2x"></i>
</button>
<div className="project-boxes jsGridView">
{!loading && records.length === 0 ? (<h4 style={{ margin: '20px' }} className='center'>No
records, sorry</h4>) : records.map((record, key) => (
<RecordItem key={key} record={record} isFilter={isFilter} filterByWhat={filterByWhat} />
))}
</div>
EDIT: > I also want to add an 'active class on each button on click. I've tried somethings but it doesn't work
I am assuming this div is where you want to toggle between jsGridView and jsListView
<div className="project-boxes jsGridView">
So why not use a state variable to store the class name? Then use the onClick even to set it.
const [cName, setClassName] = useState('jsGridView');
return (
<Fragment>
<button className="view-btn list-view" title="List View" onClick={() => setClassName('jsListView')}>
List View
</button>
<button className="view-btn list-view" title="Grid View" onClick={() => setClassName('jsGridView')}>
Grid View
</button>
<div className={"project-boxes "+cName}>
{!loading && records.length === 0 ? (<h4 style={{ margin: '20px' }} className='center'>No
records, sorry</h4>) : records.map((record, key) => (
<RecordItem key={key} record={record} isFilter={isFilter} filterByWhat={filterByWhat} />
))}
</div>
</Fragment>
)
So here you set your class to jsGridView initially so it renders in grid view by default. But you also have 2 buttons that can flip it between grid and list view.
You can also add an active class to the button if you want.
<button className={"view-btn list-view"+(cName === 'jsListView' ? ' active_btn':'')} title="List View" onClick={() => setClassName('jsListView')}>
List View
</button>
<button className={"view-btn list-view"+(cName === 'jsGridView' ? ' active_btn':'')} title="Grid View" onClick={() => setClassName('jsGridView')}>
Grid View
</button>
If one class is on, and the other is off, you can do
function toggleClass(elem) {
const classList = elem.classList;
classList.toggle('class1');
classList.toggle('class2');
}
Now the on/off status of the two classes is reversed
Also, in your Styles / CSS file, you can add :not on one class and then not need to use the other class, like
#elem.class1 {
color: ...;
font-size: ...;
}
#elem.class2, #elem:not(.class1) {
color: ...;
font-size: ...;
}
So that :not(.class1) has the same styling effects as adding class2
In React, it's uncommon to mix "vanilla" imperative JS and React like this. React provides a clean solution for toggling displayed elements that I would advise you use instead.
Take the following as an example:
// list-view.jsx
const ListView = ({ items=[] }) => {
const itemsElements = items.map(item => {
return (<li>{item}</li>)
});
return (<ul>{itemsElements}</ul>)
}
// grid-view.jsx
const GridView = ({ items=[] }) => {
const itemsElements = items.map(item => {
return (<span>{item} </span>)
});
return (<div>{itemsElements}</div>)
}
// list-grid-view.jsx
const ListGridView = ({ items=[] }) => {
const [listView, setListView] = React.useState(true);
// this fn toggles the listView variable
const toggleListView = React.useCallback(() => {
setListView(!listView);
}, [listView, setListView]);
return (
<div>
<button onClick={toggleListView} >Toggle!</button>
{listView ? <ListView items={items} /> : <GridView items={items} />}
</div>
);
}
const items = ['Hello', 'World', '!'];
const element = <ListGridView items={items} />
ReactDOM.render(element, document.getElementById('root'));
<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>
<div id="root"></div>

Hide or show mutliple Divs in Reactjs

I have created a component in my ReactJS app with a Button and a div. My goal is to press the button and show/hide the div, which currently works. But I will re-use the component so I will have multiple Buttons and divs. But I always only want one div to show.
If I press a button, hide all the divs from the same component and show the div from the button I just pressed, otherwise if the button I just pressed div is open hide it. It work the same as Bootstrap's dropdown button, but I prefer not to use Bootstrap's dropdown as I would like to create my own custom button.
I import the below Hide component in my App.js file. It works by hiding or showing the div, but would like to hide all other open div's apart from the button I currently pressed if it is not open yet.
Here is the code I currently have and use my Mycomp twice,
function hide () {
return (
<div>
<Mycomp />
<Mycomp />
</div>
);
}
function Mycomp(){
const[dp, setDp] = useState("none");
const toggle = () => {
if (dp === "none"){
setDp("block")
}else{
setDp("none")
}
}
return(
<div>
<button onClick={toggle}>Test</button>
<div style={{display: dp}}>{dp}</div>
</div>
)
}
export default hide;
You can change your component this way to get what you want . try to run this code to see the result
function Hide() {
const [visibleDivIndex, setVisibleDivIndex] = React.useState(0);
const handleVisibleDiv = id => setVisibleDivIndex(id);
const divArr = [1, 2, 3]; // just to show haw many component we will use
return (
<div>
{divArr.map((item, index) => (
<Mycomp
key={index}
id={index}
visibleDivIndex={visibleDivIndex}
handleVisibleDiv={handleVisibleDiv}
/>
))}
</div>
);
}
function Mycomp({ id, visibleDivIndex, handleVisibleDiv }) {
return (
<div>
<button onClick={e => handleVisibleDiv(id)}>Test</button>
<div style={{ display: id === visibleDivIndex ? "block" : "none" }}>
My Div Content
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<Hide />
</React.StrictMode>,
rootElement
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
The basic idea would be to move the state up to the container.
function Hide() {
const defaultDisplay = () => Array.from({length: 2}).fill(false);
const [displays, setDisplays] = useState(defaultDisplay());
function onClick(id) {
const temp = defaultDisplay();
temp[id] = true;
setDisplays(temp);
}
return (
<div>
{
displays.map((display, i) => {
return <Mycomp display={display} id={i} onClick={onClick} />;
}
}
</div>
);
}
function Mycomp({display, id, onClick}) {
const[dp, setDp] = useState(display);
useEffect(() => {
setDp(display ? 'block' : 'none');
}, [display]);
return(
<div>
<button onClick={() => onClick(id)}>Test</button>
<div style={{display: dp}}>{dp}</div>
</div>
)
}
export default Hide;

How to Change a css property based on a state of another component

I'm building a web page with gatsby which is based in react, and I need my nav component changes his sticky position to relative or auto, every time that I open the modal of the gallery component..but I don't know how to approach and solve the problem. The nav component belongs to the layout component which is Gallery's parent component...Here are the components involved:
nav component:
import React, { Component } from 'react'
import { Location } from '#reach/router'
import { Link } from 'gatsby'
import { Menu, X } from 'react-feather'
import Logo from './Logo'
import './Nav.css'
export class Navigation extends Component {
state = {
active: false,
activeSubNav: false,
currentPath: false
}
componentDidMount = () =>
this.setState({ currentPath: this.props.location.pathname })
handleMenuToggle = () => this.setState({ active: !this.state.active })
// Only close nav if it is open
handleLinkClick = () => this.state.active && this.handleMenuToggle()
toggleSubNav = subNav =>
this.setState({
activeSubNav: this.state.activeSubNav === subNav ? false : subNav
})
render() {
const { active } = this.state,
{ subNav } = this.props,
NavLink = ({ to, className, children, ...props }) => (
<Link
to={to}
className={`NavLink ${
to === this.state.currentPath ? 'active' : ''
} ${className}`}
onClick={this.handleLinkClick}
{...props}
>
{children}
</Link>
)
return (
<nav className={`Nav ${active ? 'Nav-active' : ''}`}>
<div className="Nav--Container container">
<Link to="/" onClick={this.handleLinkClick}>
<div style={{ width: `40px`, margin:`0 20px`}}>
<Logo />
</div>
</Link>
<div className="Nav--Links">
<NavLink to="/">Home</NavLink>
<NavLink to="/contact/">Contacto</NavLink>
<div className={`Nav--Group ${this.state.activeSubNav === 'about' ? 'active' : '' }`} >
<span className={`NavLink Nav--GroupParent ${
this.props.location.pathname.includes('about') ||
this.props.location.pathname.includes('team') ||
this.props.location.pathname.includes('news')
? 'active'
: ''
}`}
onClick={() => this.toggleSubNav('about')}
>
Nosotros
</span>
<div className="Nav--GroupLinks">
{subNav.map( (link, index)=> (
<NavLink
to={link.link}
key={'posts-subnav-link-' + index}
className="Nav--GroupLink">{link.name}</NavLink>
))}
</div>
</div>
</div>
<button
className="Button-blank Nav--MenuButton"
onClick={this.handleMenuToggle}
>
{active ? <X /> : <Menu />}
</button>
</div>
</nav>
)
}
}
export default ({ subNav }) => (
<Location>{route => <Navigation subNav={subNav} {...route} />}</Location>
)
the default position property is set to sticky in the nav.css file I want remove that and change it
dynamically depending of the modal gallery state, open or close.
this is my gallery component:
import React, { useState, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
const PhotoGallery = ({photos}) => {
const [currentImage, setCurrentImage] = useState(0);
const [viewerIsOpen, setViewerIsOpen] = useState(false);
const openLightbox = useCallback((event, { photo, index }) => {
setCurrentImage(index);
setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
setCurrentImage(0);
setViewerIsOpen(false);
};
return(
<div>
<Gallery photos={photos} onClick={openLightbox} />
<ModalGateway>
{viewerIsOpen ? (
<Modal onClose={closeLightbox}>
<Carousel
currentIndex={currentImage}
views={photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
)
}
export default PhotoGallery
the problem is that when the modal is open the nav still sticky and does not allow me access to the modal controls, like close and expand...and I need to change that.
There are a few approaches to this.
Old school classname toggling
Pass a prop down to the child component that reflects the state. On the child, use that prop to conditionally render one or more classes that represent the desired presentation.
Assign styles via style prop
This is similar to #1, but eliminates a layer of abstraction. Instead of assembling a class list you just assemble the CSS styles you'd like to apply as an object.
const Component = ({ someState }) =>
<div style={someState ? { border: "5px solid red" } : { color: "#999" }}>
Some Text
</div>
Use a CSS-in-JS library
The downside of the above approach is that you wind up duplicating styles for each instance of your element on the page. CSS-in-JS libraries solve this by extracting your styles into an automatically generated class and applying the class to your component instead. I prefer Emotion, but there are others.
Using Emotion you're able to accept a className prop from the parent that override the defaults set by the child. This inversion-of-control is really powerful and solves many of the shortcomings with early CSS-in-JS approaches.
const ParentComponent = () => {
const [someState] = useState(false)
return <ChildComponent css={{ color: someState ? "blue" : "red" }} />
}
const ChildComponent = ({ className }) =>
<div
css={{
color: "#000",
border: "4px solid currentColor"
}}
className={className}
>
Some Text
</div>
In the above example, className is assigned by Emotion using the generated class name assigned based on the css prop passed to ChildComponent inside of ParentComponent. The result of this would be a div with a blue border and blue text when someState is false (default). When someState is switched to true, the border and text will be red. This is because the styles passed in via className will override the styles assigned directly via css in Emotion.

Categories