1

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

7
  • Please create stackblitz to reproduce the issue. Commented Dec 31, 2018 at 6:25
  • @Justcode thank you for your quick response. I'll try Commented Dec 31, 2018 at 6:47
  • @Justcode I've just created one as you suggested. Check the updated post. Cheers Commented Dec 31, 2018 at 7:01
  • Not able to reproduce in your link Commented Dec 31, 2018 at 7:09
  • @Justcode if you move your mouse as fast as you can between the two divs, it will reproduce. You will get view details displaying Commented Dec 31, 2018 at 7:16

3 Answers 3

2

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

Sign up to request clarification or add additional context in comments.

5 Comments

This does not solve the problem, on hovering out e.g. first item is still kept in state
I suspect it's the way you use ReactHoverObserver, updated my answer.
Now you removed setState from your code, why? Please don't go the easy way. It is not what OP has asked.
@loelsonk You don't need to manually setState when you're using ReactHoverObserver package. It's just how the package works. If OP wants manual way, he should just not use any package and manually write the handler for onMouseEnter and onMouseLeave. Check out the stackblitz demo and you will see it all work.
@LirenYeo thank you for your quick answer. This actually makes it less complicated. I wonder why though if you are using an array, it's not as accurate as if they have their own hover action?
1

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

1 Comment

Thank you. I'll try your suggestion along with Liren's answer as well
0

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})
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.