How can I prevent the onmouseleave event from being triggered outside the browser? - javascript

There is a basket. When i enter the mouse, the dropdown opens and shows the items in the basket. When i leave the mouse, the dropdown closes. Inside the dropdown, i have a button for each item to remove items from the basket. When we click the button, a modal pops up, and we approve or reject. When this modal pops up, if we move the mouse away from the browser, onMouseEnter event is triggering and and causing the modal to close. How can i prevent this? I want the modal to close only when I click the backdrop or the reject button.
I passed hideDropdown function to child component(BasketItem) and tried to update isModalShow and dropdown state the same handleClick function. The dropdown closed, but the modal didn't pop up. How can i solve this? Can you help?
Basket component:
const Basket = () => {
const [isDropdownShow, setIsDropdownShow] = useState(false);
const { basket } = useAppContext();
const showDropdown = (e) => {
setIsDropdownShow(true);
};
const hideDropdown = (e) => {
setIsDropdownShow(false);
};
return (
<Wrapper
onMouseEnter={showDropdown}
onMouseLeave={hideDropdown}
isDropdownShow={isDropdownShow}
>
Basket
<span className='basket-length'>{basket.length}</span>
{isDropdownShow && <div className='white-border'></div>}
{basket.length === 0 && isDropdownShow && (
<div className='dropdown empty-basket'>Basket is empty</div>
)}
{isDropdownShow && (
<div className='dropdown'>
<ul>
{basket
.map((basketItem, index) => {
return (
<BasketItem
key={index}
basketItem={basketItem}
hideDropdown={hideDropdown}
/>
);
})
.sort(sortBasket)}
</ul>
</div>
)}
</Wrapper>
);
};
export default Basket;
BasketItem Component:
const BasketItem = ({ basketItem, hideDropdown }) => {
const [isModalShow, setIsModalShow] = useState(false);
const handleClick = () => {
hideDropdown();
setIsModalShow(true); //does not trigger
};
return (
<Wrapper>
<img src={basketItem.image} alt='product-item' />
<div>
<p className='basket-description'>{basketItem.description}</p>
<span className='basket-remove' onClick={handleClick}>
Remove
</span>
<Modal
isModalShow={isModalShow}
onClose={() => setIsModalShow(false)}
/>
</div>
</Wrapper>
);
};
export default BasketItem;
Modal:
const Modal = ({ isModalShow, onClose }) => {
if (!isModalShow) return null;
return ReactDom.createPortal(
<>
<Backdrop />
<Wrapper>
<button onClick={onClose}>CloseModal</button>
some content
</Wrapper>
</>,
document.getElementById('overlays')
);
};
export default Modal;

Related

How to handle outside and inside clicks of DIV in conditionally rendered react functional component?

I'm midway through creating shopping cart of some website. I want the shopping cart to close when clicked outside of it. I managed to get it working, but for some reason it still closes when clicked on the div of the cart. I managed to find the problem. But i need help solving it. The logic behind it is that when website is clicked I get the event using listener and try to find if ref contains the click. Here's the code:
function CartItem(props) {
// handling outside clicks
const [open, setOpen] = useState(false);
const cartRef = useRef(null);
useEffect(() => {
document.addEventListener("click", handleClickOutside, true)
}, []);
const handleClickOutside = (e) => {
if(!cartRef.current.contains(e.target)) {
setOpen(false);
}
}
return (
<div className='nav-item' ref={cartRef} onClick={() => setOpen(!open)} >
<div className='navItemImage'>
<img src={CartImage} alt="Shopping Cart Icon" title="Shopping Cart Icon"/>
</div>
{open && props.children}
</div>
);
}
Now the problem is that when click happens on the opened nav-item DIV (which is the shopping cart DIV) props.children isn't rendered, thus .contains is false and changes the state. What can I do to solve this problem. Thanks!
Here is the full code for context:
import React, { useEffect, useState, useRef } from 'react';
import "./Cart.css";
import CartImage from "../../assets/Cart.svg";
import { findCurrencySymbol } from "../../constructors/functions"
import { findAmount } from "../../constructors/functions"
function Cart(props) {
return(
<CartItem >
<CartMenu cart={props.cart} currencyLabel={props.currencyLabel}/>
</CartItem>
);
}
function CartItem(props) {
// handling outside clicks
const [open, setOpen] = useState(false);
const cartRef = useRef(null);
useEffect(() => {
document.addEventListener("click", handleClickOutside, true)
}, []);
const handleClickOutside = (e) => {
console.log(cartRef.current);
console.log(e.target);
if(!cartRef.current.contains(e.target)) {
setOpen(false);
}
}
return (
<div className='nav-item' ref={cartRef} onClick={() => setOpen(!open)} >
<div className='navItemImage'>
<img src={CartImage} alt="Shopping Cart Icon" title="Shopping Cart Icon"/>
</div>
{open && props.children}
</div>
);
}
function CartMenu(props) {
function CartMenuItem(props) {
return (
<div className='cart-item'>
<h1 className='cart-header'>My bag, <span>{props.cart.length} items</span></h1>
<ul className='cart-products'>
{
props.cart.map((product) => {
return (
<li className='cart-product' key={product}>
<div className='cart-left'>
<h2 className='cart-product-title'>{product.brand}</h2>
<h2 className='cart-product-title'>{product.name}</h2>
<h2 className='cart-product-price'>{findCurrencySymbol(product.prices, props.currencyLabel)} {findAmount(product.prices, props.currencyLabel)}</h2>
{/* {
product.category.map((item) => {
return (
console.log(item)
);
})
} */}
{/* {console.log(product)} */}
</div>
<div className='cart-middle'>
<div className='quantity-increase' onClick={() => increaseQuantity()}>+</div>
<h2>{product.quantity}</h2>
<div className='quantity-decrease' onClick={() => decreaseQuantity()}>-</div>
</div>
<div className='cart-right'>
<img src={product.image} />
</div>
</li>
);
})
}
</ul>
</div>
);
}
return (
<div className='dropdown-cart'>
{props.cart.map((item) => {
return (
<CartMenuItem cart={props.cart} key={item} currencyLabel={props.currencyLabel}/>
);
})}
</div>
);
}
function increaseQuantity() {
console.log(+1);
}
function decreaseQuantity() {
console.log(-1);
}
export default Cart;
Here is the console logged cartRef and e.target:
I tried using forwarding refs but got similar results

How can i do to wrap different component with the same modal

I'm trying to wrap a modal in a different component but only the first showed up.
So I searched for a way to code my modal so I can accept different component open and close the right component instead of open the first component even if i click in another button
{open && <Modal>
<TeamForm />
</Modal>}
{openSavecard && <Modal>
<HikingSaveCards items={cartItems}/>
</Modal>}
// only the first one will display
const Modal = ({children}: Props) => {
const ContentRef = useRef<HTMLDivElement>(null)
const [open,setOpen] = useState(true)
useEffect(() => {
if(!open) return ;
function listener(e:any) {
if (ContentRef.current?.contains(e.target)) return;
setOpen(prev => !prev)
}
window.addEventListener('click', listener)
return () =>{ window.removeEventListener('click', listener)};
}, [open])
return open ? createPortal(
<>
<Background >
<Content ref={ContentRef}>
{children}
</Content>
</Background>
</>, document.querySelector('#portal')!) : null
};
export default Modal

How to render a button in parent component based on children action in react js?

I have a list of movie cards when a user clicks on them, they become selected and the id of each movie card is transferred to an array named "selectedList".
I want to add a "let's go" button below the movie card but conditionally.
I mean when the array is empty the button should not be displayed and when the user clicked on at least a movie the button displays. the array should be checked each time and whenever it becomes equal to zero the button should disappear.
the thing is all the movie cards are the children of this page and I want to render the parent component based on children's behavior.
MY MAIN PAGE:
export default function Index(data) {
const info = data.data.body.result;
const selectedList = [];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{info.map((movie, i) => {
return (
<MovieCard
movieName={movie.name}
key={i}
movieId={movie._id}
selected={selectedList}
isSelected={false}
/>
);
})}
</div>
</main>
<button className={style.done}>Let's go!</button>
</>
);
}
**MOVIE CARD COMPONENT:**
export default function Index({ selected, movieName, movieId, visibility }) {
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
}
toggleClass();
};
return (
<div>
<img
className={isActive ? style.movie_selected : style.movie}
src={`images/movies/${movieName}.jpg`}
alt={movieName}
id={movieId}
onClick={pushToSelected}
/>
<h3 className={style.title}>{movieName}</h3>
</div>
);
}
You can use conditional rendering for that:
{selectedList.length > 0 && <button className={style.done}>Let's go!</button>}
Plus, you should change your selectedList to a state, and manage the update via the setSelectedList function:
import { useState } from 'react';
export default function Index(data) {
const info = data.data.body.result;
const [selectedList, setSelectedList] = useState([]);
Add the method to the MovieCardĀ as a property:
<MovieCard
movieName={movie.name}
key={i}
movieId={movie._id}
selected={selectedList}
setSelected={setSelectedList}
isSelected={false}
/>;
And update the list in the pushToSelected method:
export default function MovieCard({
selected,
setSelected,
movieName,
movieId,
visibility
}) {
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
}
setSelected([...selected]);
toggleClass();
};

Rendering different images in a modal based on what button is clicked

I would like to change the content inside a modal based on what button is clicked on.
Depending on the button clicked, a different array of images will be provided to render new <img> tags in the modal.
From my understanding the useState hook should be used here along with map to render each image from the provided image array.
Below is a simplified version of my current code.
const ModalGallery = () => {
...
const [ modalContent, setModalContent ] = useState([]);
const loadImages = imageArray => {
// code that uses map and setModalContent ?
};
return (
<>
{projects.map(project => {
return (
<Button
onClick={() => {
// call a funtion that changes the images rendered in the modal
loadImages(project.images);
}}
>
Display
</Button>
);
})}
<Modal>
{modalContent}
</Modal>
</>
);
};
export default ModalGallery;
You should maintain image urls in modalContent, then on click, change modalContent with image url's of corresponding project, which cause the Modal element to get updated.
const ModalGallery = () => {
...
const [modalContent, setModalContent] = useState([]);
const [showModal, setShowModal] = useState(false)
const modal = () => {
return(
<Model>
{
modalContent.map((img_url) =>{
return(<img src={img_url} key={img_url} />)
})
}
</Model>
)
}
return (
<>
{projects.map(project => {
return (
<Button
onClick={() => {
// call a funtion that changes the images rendered in the modal
setModalContent(project.images);
setShowModal(true)
}}
>
Display
</Button>
);
})}
{showModal ? modal : null}
</>
);
};

How to toggle a class on the body with React onClick

I have a button that opens a div that looks like a modal.
On click of this button, I want to add a class on to the body, then on click of a close button, I want to remove this class.
How would you do this in React functional component with useEffect / useState?
You could use this.setState like this in Components
....
handleClick = () => {
this.setState((state) => {
addClass: !state.addClass
})
}
render () {
const { addClass } = this.state
return (
<>
<button onClick={this.handleClick}>Button</button>
<div className={!!addClass && 'yourClassName'}>hello</div>
<>
)
}
And in function like this
function Example() {
const [addClassValue, addClass] = useState(0);
return (
<div>
<button onClick={() => addClass(!addClassValue)}>
Click me
</button>
<div className={addClass ? 'yourClassName'}>hello</div>
</div>
)
}
But you do not need classes because React works with components and you can show your modal like
function Example() {
const [addClassValue, addClass] = useState(false);
return (
<div>
<button onClick={() => addClass(!addClassValue)}>
Click me
</button>
// this Component will be only rendered if addClassValue is true
{addClassValue && <ModalComponent />}
</div>
)
}

Categories

Resources