I have reactJs app and I have made a custom dropDown with a div that I set an onClick attribute to open dropDown and close it.
but also I want to close it when the user clicks to another part of the site.
<div
onClick={() => setNoneQuote(noneQuote ? false : true)}
className="selected-drop-down"
>
<span className="dropDownText">{selectedQuoteCurrency}</span>
<img
className="dropDownIcon"
src={require("../assets/image/arrow/dropDown-Arrow.png")}
width="15px"
alt="arrow"
/>
</div>
I try onMouseDown instead of onClick according to this answer ==> stackoverflow ,but I don't know why it doesn't work for me :(
const dropdownElement= document.querySelector("choose an id or a specific class")
window.onclick = function(event) {
if (event.target !== dropdownElement) {
dropdownElement.style.display = "none"; // or any other function that you want to call
}
}
this may not be the exact code to fix your problem but you can use the logic
You could do it in different ways, but I will show you solution that I used in one of my projects.
So, I used <header> and <main> tags where I had all my Components. And in those tags i used eventListener with a callback function, like this:
<header onClick={handleClick}>
<Nav />
<Greeting />
<Sidebar />
</header>
and
<main onClick={handleClick}>
<Description />
<Icons />
<Prices />
<Gallery />
<NecessaryInfo />
<Location />
<GalleryModal />
<ConditionsModal />
</main>
In a callback function handleClick I checked where user could click and to do that I used next logic of pure JS:
!e.target.classList.contains("menu-open") &&
!e.target.classList.contains("menu-links") &&
!e.target.parentElement.classList.contains("menu-links") &&
!e.target.parentElement.parentElement.classList.contains("menu-links") &&
closeSidebar();
Function closeSidebar() is simple:
const closeSidebar = () => {
setIsSidebarOpen(false)};
In your code instead of using setNoneQuote(noneQuote ? false : true) you could also use: setNoneQuote(!noneQuote). Exclamation mark before value always will change it to the oposite.
Related
The situation is a bit complicated:
inside a component called "LeftSectionHeader" I have a div, which when clicked must render a component;
the component to be rendered is called "ProfileMenu", and is basically a div that must be rendered on top of "LeftSectionHeader" itself and another div;
All these components are rendered inside another component called "Main".
The problem is that if I define the function inside "LeftSectionHeader", "ProfileMenu" will be rendered inside, while I need it to not only be rendered outside, but even cover it; that's why you'll see some boolean vars inside "Main", because that is the only way i could render it, but it still doesn't cover the other divs. I'll attach the code of each component and how the final result should look below.
LeftSctionHeader:
function LeftSectionHeader(){
return(
<div class="left-section-header">
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
The div belonging to the "crop" class is the one that must be clicked to render "ProfileMenu"
ProfileMenu:
function ProfileMenu(){
return(
<div class="profile-side-menu">
//A lot of boring stuff
</div>
);
}
There are some functions related to this component, but they are not important, so I didn't put them, just ignore it
Main:
var p=true;
var m=true;
function Main(){
return(
<div class="main">
<Header />
<div class="left-section">
{m ? <div><LeftSectionHeader /><LangMenu /></div> : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
Before clicking on the orange div
After clicking
This might help as guidline, hopefully!
function LeftSectionHeader({ onClick }){
return(
<div class="left-section-header" onClick={onClick}>
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
function Main(){
const [showProfile, setShowProfile] = useState(false);
return(
<div class="main">
<Header />
<div class="left-section">
{!showProfile ? (
<div>
<LeftSectionHeader onClick={() => setShowProfile(true)} />
<LangMenu />
</div>
) : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
The simplest solution might be to pass a handler into the header component to toggle the menu:
function App () {
const [showMenu, setShowMenu] = useState();
return (
<div>
<Header onMenuToggle={() => setShowMenu(!showMenu)} />
{ showMenu && <Menu /> }
</div>
)
}
function Header ({ onMenuToggle }) {
<div onClick={onMenuToggle}>...</div>
}
Caveat: This will cause the entire App component to re-render when the menu state changes. You can mitigate this by either
A) placing the menu state closer to where it's actually needed, like in the sidebar component instead of at the top, or
B) using a context or other orthogonal state store.
Another approach would be to leave the state handling in the LeftSectionHeader component and then use a React portal to render the menu elsewhere in the DOM.
I am designing my website in ReactJS and I'm having an issue. The desktop version of the website works perfectly! The mobile version, however, has one problem: my input field claims to have no value! I'll post the code below, but basically the desktop version has the email signup in the toolbar at the top (this is where it works) and the mobile version has it in the sidedrawer, where my links are (doesn't work here).
App.js
addEmailSub = () => {
var email = document.getElementById("email").value;
mailerlite.addSubscriber(104625980, email).then(() => {
this.setState({isSubscribed: true});
});
}
render() {
var backdrop;
if(this.state.sideDrawerOpen) {
backdrop = <Backdrop click={this.backdropClickHandler}/>;
}
return(
<div>
<Toolbar drawerClickHandler={this.drawerToggleClickHandler} addEmailSub={this.addEmailSub} isSubscribed={this.state.isSubscribed}/>
<SideDrawer show={this.state.sideDrawerOpen} addEmailSub={this.addEmailSub} isSubscribed={this.state.isSubscribed}/>
{backdrop}
<Switch>
<Route path="/books" component={Books} />
<Route path="/events" component={Events} />
<Route path="*" component={About} />
</Switch>
</div>
);
}
}
Toolbar.js (this is where the signup component works)
const toolbar = props => (
<header className="toolbar">
<nav className="toolbar-navigation">
<div className="toolbar-toggle">
<DrawerToggleButton click={props.drawerClickHandler}/>
</div>
<div className="toolbar-logo">
<img className="logo" src={logo}></img>
</div>
<SocialMedia />
<div className="spacer" />
<div className="signup">
<Signup addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this works
</div>
<div className="spacer" />
<div className="toolbar-nav-items">
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/books">Books</Link></li>
<li><Link to="/events">Events</Link></li>
</ul>
</div>
</nav>
</header>
);
Sidedrawer.js
const sideDrawer = props => {
var drawerClasses = 'side-drawer';
if(props.show) {
drawerClasses = 'side-drawer open';
}
return(
<nav className={drawerClasses}>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/books">Books</Link></li>
<li><Link to="/events">Events</Link></li>
</ul>
<div className="side-drawer-signup">
<Signup addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this does not work
</div>
</nav>
);
};
Signup.js
const signup = props => {
if(props.isSubscribed) {
return(<div>
<h3 className="signup-thanks">Thanks for signing up! Check your email :)</h3>
</div>)
}
else{
return(<div>
<input id="email" className="signup-input" placeholder="Enter your email address"></input>
<button onClick={props.addEmailSub} className="signup-button">Beta Read!</button>
</div>);
}
}
Unless I forgot how to read, I am positive I passed down all the props and functions appropriately, but the Signup component in the Sidedrawer doesn't capture any form data. I posted on Reddit and have searched the first few pages of Stackoverflow, but I couldn't find anything pertaining to my exact issue: where the input field captures text in one spot but not the other.
EDIT:
Thanks to yjay, I got it working properly! They were correct, it was looking for the first input id of "email". I thought it would be treated as the same input box rendered in a different spot, but it actually treats it as two separate inputs. I will post how I fixed it here:
App.js
addEmailSub = () => {
var email = document.getElementById("email").value;
if (!email) { //this checks if the first input box has no value
email = document.getElementById("sidebar-email").value; //this tells the website to look at the other input box
}
console.log('value of email is: ' + email); //VINDICATION
mailerlite.addSubscriber(104625980, email).then(() => {
this.setState({isSubscribed: true});
});
}
Toolbar.js
<Signup inputID="email" addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this is all I changed from above. Added inputID prop
Sidedrawer.js
<Signup inputID="sidebar-email" addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this is all I changed here, just like toolbar, adding inputID as a prop with a different id
Signup.js
return(<div>
<input id={props.inputID} className="signup-input" placeholder="Enter your email address"></input> //here I use the prop to dynamically change the id based on where it's being rendered
<button onClick={props.addEmailSub} className="signup-button">Beta Read!</button>
</div>);
Fixed just in time, thank you yjay!
It seem you have two elements with same id "email". It makes your html invalid and it seems to force getElementById, which return one elem, to choose. My guess is it has chosen the one from Toolbar (prolly cause it's higher). I think you should show one of them conditionally or give them different IDs.
I want to input a picture in my Lightbox, but from what I see the problem is that the CSS is applied before the img source can be loaded inside of the tag
Problem: The Lightbox Activates without an image source.
It only works if I put an img source manually.
class LightboxTest extends Component {
state = {};
render() {
return (<React.Fragment>
<div
className={this.props.setActivity === false ? 'lightboxOff' : this.props.setActivity === true ? 'lightboxActive' : ''}
>
<img alt="" src={this.props.lbImages} />
</div>
</React.Fragment>);
}
}
export default LightboxTest;
I'm passing a boolean 'setActivity' per props to check whether an image has been clicked on to set the Lightbox Active and 'lbImages' as my source which should be loaded, when clicked on an image.
<div className="header">
{images.map((image) => (
<React.Fragment key={image.id}>
<img
alt=""
src={image.src}
key={image.id}
onClick={() => {
setLightboxImage(image.src);
}}
onClick={handleClick}
/>
</React.Fragment>
))}
</div>
When I remove the lightbox div tag the image appears with no issue when Clicked on.
Can I set like a priority?
you have two onClick handlers, so the second one isn't executed. Can be changed to:
onClick={() => {
setLightboxImage(image.src);
handleClick();
}}
I'm trying to display a div when the mouse is over another div element. I've managed to do so via onMouseEnter and onMouseLeave.
The issue here is that if you quickly move from one div to another (it's an array of divs that contain data about a product), the value of index[0] becomes true.
The way it works is that I have an array initialised to false when the mouse enters one of them, it becomes true and shows the div that I wanted. Once it leaves, it set it back to false.
this.state = {
isProductsHovering: new Array(this.props.currentProducts.length).fill(false)
};
handleMouseHover = (idx) => {
this.setState({
isProductsHovering: update(this.state.isProductsHovering, {
[idx]: { $set: !this.state.isProductsHovering[idx] }
})
})
}
render() {
return this.props.currentProducts.map((product, idx) => {
return <Fragment key={idx}>
<div className="product-grid-view col-6 col-md-4" >
<div
className=" product-holder"
onMouseEnter={this.handleMouseHover.bind(this, idx)}
onMouseLeave={this.handleMouseHover.bind(this, idx)}>
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
this.state.isProductsHovering[idx] &&
<div className="product-buttons">
<Link to={`products/${product.id}`} className="btn-detail" text="View Details" />
<Link to='#' className="btn-cart" icons={["icon-cart", "icon-plus"]} />
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<Stars values={product.averageRating} {...starsRating} />
<span className="product-review">{getLength(product.reviews)} review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">SR</span>
</div>
</div>
</div>
</Fragment>
})
}
Update
I've made a stackblitz project to reproduce the same issue as suggested:
https://stackblitz.com/edit/react-mouse-hover.
For everyone that wants to see what I mean. I've attached a photo of the issue. If you move the mouse over the two divs (up and down as quick as you can), this what happens:
mouse hover broken
For situation like this, I wouldn't rely on array and index to make it work. You are further complicating your handleMouseHover functions and the checking of isHovering.
A 'more React' way of dealing with this situation is simply make each Product a component itself. And this Product component will have its own state of isHovered and handleOnHover method, that way you create a more concise and reliable code without having to rely on array index at all:
App.js can be as simple as this:
class App extends Component {
render() {
return (
<div>
{
data.map(product =>
<Product product={product} />
)
}
</div>
)
}
}
A new Product.js:
import React from 'react'
import ReactHoverObserver from 'react-hover-observer';
export default class Product extends React.Component {
render() {
const { product } = this.props
return (
<ReactHoverObserver className="product-grid-view col-6 col-md-4">
{
({isHovering}) => (
<div className=" product-holder">
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
isHovering &&
<div className="product-buttons">
<button className="btn-detail">View Details</button>
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<span className="product-review">0 review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">Currency</span>
</div>
</div>
)
}
</ReactHoverObserver>
)
}
}
I have put the moficiation in Stackblitz: https://stackblitz.com/edit/react-mouse-hover-2cad4n
Liren's answer is good advice and will help simplify the code. One thing I also noticed is that occasionally the HoverObserver won't 'hear' an event, and since the hover enter and hover exit events are listening to the same event, then the display state for the button will become reversed (i.e., it will show when the mouse is NOT hovering and hide when the mouse hovers over the observer).
I would recommend getting rid of the ReactHoverObserver HOC and instead just listen for the onMouseOver for hover enter and onMouseLeave for hover exit. That way, even if the div doesn't register a hover enter or exit, it will easily reset because onMouseOver will toggle the display state to true and onMouseLeave will reliably set the button's display state to false.
See here for those events in the docs:
https://reactjs.org/docs/events.html#mouse-events
The way you trigger it (from array or from a component) is semantics , the real issue is that these events don't always fire.
I had the same issue , apparently the events of react are not that reliable.
In my case I could live in a situation where a tooltip does not close , but not in the situation where 2 tooltips are open. In order to solve my issue , I returned to good old dom manipulation - Every time a tooltip appeared , it made all the other ones invisible.
It looked something like this :
showTooltip() {
// Clear old tooltips, EventTooltip is the name of my tooltip class.
Array.from(document.getElementsByClassName('EventTooltip'))
.forEach(tooltip=>tooltip.style = 'display:none;')
this.setState({showTooltip: true})
}
hideTooltip() {
this.setState({showTooltip: false})
}
I have a simple modal component:
function Modal(props) {
return (
<div className={cx(styles.overlay, { show: props.show })} onClick={props.onClose}>
<div className={styles.modal}>
<span className={styles.closeBtn} onClick={props.onClose} />
{props.children}
</div>
</div>
)
}
the onClose prop triggers closing the modal, hence I attach it to styles.overlay (dark overlay that you typically see on modals that when clicked dissmises it) and to styles.closeBtn (a close button for modal).
The whole flow works besides the fact that anything inside styles.overlay when clicked on also dismisses the modal, which is not functionality I was after, hence I need to only dismiss it if that specific element is clicked not its children.
function Modal(props) {
return (
<div className={cx(styles.overlay, { show: props.show })} onClick= {props.onClose}>
<div className={styles.modal} onClick={e => e.preventDefault()}>
<span className={styles.closeBtn} onClick={props.onClose} />
{props.children}
</div>
</div>
)
}
I think, the best way is to have your overlay and your modal in two separate div, but this should work.
Add onClick(e)={e.stopPropagation();} to the modal dialog's click handler; this should prevent it from propagating to the overlay.
Hope it works! Good luck!