146

How can you achieve either a hover event or active event in ReactJS when you do inline styling?

I've found that the onMouseEnter, onMouseLeave approach is buggy, so hoping there is another way to do it.

Specifically, if you mouse over a component very quickly, only the onMouseEnter event is registered. The onMouseLeave never fires, and thus can't update state... leaving the component to appear as if it still is being hovered over. I've noticed the same thing if you try and mimic the ":active" css pseudo-class. If you click really fast, only the onMouseDown event will register. The onMouseUp event will be ignored... leaving the component appearing active.

Here is a JSFiddle showing the problem: https://jsfiddle.net/y9swecyu/5/

The code:

var Hover = React.createClass({
    getInitialState: function() {
        return {
            hover: false
        };
    },
    onMouseEnterHandler: function() {
        this.setState({
            hover: true
        });
        console.log('enter');
    },
    onMouseLeaveHandler: function() {
        this.setState({
            hover: false
        });
        console.log('leave');
    },
    render: function() {
        var inner = normal;
        if(this.state.hover) {
            inner = hover;
        }

        return (
            <div style={outer}>
                <div style={inner}
                    onMouseEnter={this.onMouseEnterHandler}
                    onMouseLeave={this.onMouseLeaveHandler} >
                    {this.props.children}
                </div>
            </div>
        );
    }
});

var outer = {
    height: '120px',
    width: '200px',
    margin: '100px',
    backgroundColor: 'green',
    cursor: 'pointer',
    position: 'relative'
}

var normal = {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'red',
    opacity: 0
}

var hover = {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'red',
    opacity: 1
}

React.render(
    <Hover></Hover>,         
    document.getElementById('container')
)
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197

14 Answers14

129

Have you tried any of these?

onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp

SyntheticEvent

it also mentions the following:

React normalizes events so that they have consistent properties across different browsers.

The event handlers below are triggered by an event in the bubbling phase. To register an event handler for the capture phase, append Capture to the event name; for example, instead of using onClick, you would use onClickCapture to handle the click event in the capture phase.

Edgar
  • 6,022
  • 8
  • 33
  • 66
Elon Zito
  • 2,872
  • 1
  • 24
  • 28
  • 9
    Just to mention for onMouseEnter and onMouseLeave, from the docs: `The onMouseEnter and onMouseLeave events propagate from the element being left to the one being entered instead of ordinary bubbling and do not have a capture phase.` – P Fuster Nov 06 '18 at 18:52
45

The previous answers are pretty confusing. You don't need a react-state to solve this, nor any special external lib. It can be achieved with pure css/sass:

The style:

.hover {
  position: relative;

  &:hover &__no-hover {
    opacity: 0;
  }

  &:hover &__hover {
    opacity: 1;
  }

  &__hover {
    position: absolute;
    top: 0;
    opacity: 0;
  }

  &__no-hover {
    opacity: 1;
  }
}

The React-Component

A simple Hover Pure-Rendering-Function:

const Hover = ({ onHover, children }) => (
    <div className="hover">
        <div className="hover__no-hover">{children}</div>
        <div className="hover__hover">{onHover}</div>
    </div>
)

Usage

Then use it like this:

    <Hover onHover={<div> Show this on hover </div>}>
        <div> Show on no hover </div>
    </Hover>
dreampulse
  • 1,038
  • 10
  • 9
30

you can use onMouseOver={this.onToggleOpen} and onMouseOut={this.onToggleOpen} to muse over and out on component

Behnam Eskandari
  • 1,011
  • 12
  • 27
  • It worked perfectly for me, thanks a lot. But, how can I access to that other functions like I for example would exists "onMauseOverFirstTime"? Where did you get that information from? – SmoggeR_js Mar 22 '19 at 12:51
  • 1
    @MtgKhaJeskai https://reactjs.org/docs/events.html If you want sth like onMouseOverFirstTime you'd have to create it yourself, for example, make the function only fire when 'firstMouseOver' in your state is true and set it to false when the function was called once. – cubefox Mar 25 '19 at 11:17
  • I founded! Thank you very much! :D – SmoggeR_js Mar 25 '19 at 11:35
  • No, it's not exists "onMauseOverFirstTime" function; But you can use a flag to do this action, add this to your states " states :{ isFirstTime:false} " and make it true in "onMouseOver" @MtgKhaJeskai – Behnam Eskandari Mar 28 '19 at 09:31
15

Note: This answer was for a previous version of this question where the question asker was trying to use JavaScript to apply css styles… which can simply be done with CSS.

A simple css-only solution.

For applying basic styles, CSS is simpler and more performant that JS solutions 99% of the time. (Though more modern CSS-in-JS solutions — eg. React Components, etc — are arguably more maintainable.)

Run this code snippet to see it in action…

.hover-button .hover-button--on,
.hover-button:hover .hover-button--off {
  display: none;
}

.hover-button:hover .hover-button--on {
  display: inline;
}
<button class='hover-button'>
  <span class='hover-button--off'>Default</span>
  <span class='hover-button--on'>Hover!</span>
</button>
Beau Smith
  • 33,433
  • 13
  • 94
  • 101
  • 14
    This does not answer the question. – tsujin Jul 01 '18 at 17:46
  • @tsujin - see the note above. – Beau Smith Jul 02 '18 at 19:17
  • `more performant that JS solutions 99% of the time` not true. read articles debunking this myth. do you have any source? – Claudiu Creanga Mar 06 '19 at 10:34
  • @ClaudiuCreanga - Please link to articles debunking this myth. – Beau Smith Mar 06 '19 at 19:51
  • @BeauSmith https://developer.mozilla.org/en-US/docs/Web/Performance/CSS_JavaScript_animation_performance `The fact is that, in most cases, the performance of CSS-based animations is almost the same as JavaScripted animations — in Firefox at least. Some JavaScript-based animation libraries, like GSAP and Velocity.JS, even claim that they are able to achieve better performance than native CSS transitions/animations.` – Claudiu Creanga Mar 07 '19 at 10:56
  • 1
    @ClaudiuCreanga - I've clarified the sentence you objected to, to be more concise. – Beau Smith Apr 10 '19 at 05:27
12

If you can produce a small demo showing the onMouseEnter / onMouseLeave or onMouseDown / onMouseUp bug, it would be worthwhile to post it to ReactJS's issues page or mailing list, just to raise the question and hear what the developers have to say about it.

In your use case, you seem to imply that CSS :hover and :active states would be enough for your purposes, so I suggest you use them. CSS is orders of magnitude faster and more reliable than Javascript, because it's directly implemented in the browser.

However, :hover and :active states cannot be specified in inline styles. What you can do is assign an ID or a class name to your elements and write your styles either in a stylesheet, if they are somewhat constant in your application, or in a dynamically generated <style> tag.

Here's an example of the latter technique: https://jsfiddle.net/ors1vos9/

Tobia
  • 17,856
  • 6
  • 74
  • 93
  • @clusterBuddy I suppose it's a bug in JsFiddle or in the libraries, because the code is correct and has always worked fine. – Tobia Jun 04 '19 at 14:15
9

I've just bumped into this same problem when listening for onMouseLeave events on a disabled button. I worked around it by listening for the native mouseleave event on an element that wraps the disabled button.

componentDidMount() {
    this.watchForNativeMouseLeave();
},
componentDidUpdate() {
    this.watchForNativeMouseLeave();
},
// onMouseLeave doesn't work well on disabled elements
// https://github.com/facebook/react/issues/4251
watchForNativeMouseLeave() {
    this.refs.hoverElement.addEventListener('mouseleave', () => {
        if (this.props.disabled) {
            this.handleMouseOut();
        }
    });
},
render() {
    return (
        <span ref='hoverElement'
            onMouseEnter={this.handleMouseEnter}
            onMouseLeave={this.handleMouseLeave}
        >
            <button disabled={this.props.disabled}>Submit</button>
        </span>
    );
}

Here's a fiddle https://jsfiddle.net/qfLzkz5x/8/

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
  • This approach won't work, because there is no way to get reliable the `onMouseLeave`-Event – dreampulse Dec 14 '16 at 15:09
  • Did you check the fiddle? Seems to work fine for me, except that the event is thrown twice when you mouse out. – Cory Danielson Dec 14 '16 at 16:45
  • It will work quite often, but not always! See this discussion: http://stackoverflow.com/questions/7448468/why-cant-i-reliably-capture-a-mouseout-event – dreampulse Jan 02 '17 at 16:13
7

I'd use onMouseOver & onMouseOut. Cause in React:

The onMouseEnter and onMouseLeave events propagate from the element being left to the one being entered instead of ordinary bubbling and do not have a capture phase.

Here it is in the React documentation for mouse events.

hlkughl
  • 326
  • 2
  • 3
5

A package called styled-components can solve this problem in an ELEGANT way.

Reference

  1. Glen Maddern - Styling React Apps with Styled Components

Example

const styled = styled.default
const Square = styled.div`
  height: 120px;
  width: 200px;
  margin: 100px;
  background-color: green;
  cursor: pointer;
  position: relative;
  &:hover {
    background-color: red;
  };
`
class Application extends React.Component {
  render() {
    return (
      <Square>
      </Square>
    )
  }
}

/*
 * Render the above component into the div#app
 */
ReactDOM.render(<Application />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id='app'></div>
Karl Xu
  • 315
  • 5
  • 15
2

You can't with inline styling alone. Do not recommend reimplementing CSS features in JavaScript we already have a language that is extremely powerful and incredibly fast built for this use case -- CSS. So use it! Made Style It to assist.

npm install style-it --save

Functional Syntax (JSFIDDLE)

import React from 'react';
import Style from 'style-it';

class Intro extends React.Component {
  render() {
    return Style.it(`
      .intro:hover {
        color: red;
      }

    `,
      <p className="intro">CSS-in-JS made simple -- just Style It.</p>
    );
  }
}

export default Intro;

JSX Syntax (JSFIDDLE)

import React from 'react';
import Style from 'style-it';

class Intro extends React.Component {
  render() {
    return (
      <Style>
      {`
        .intro:hover {
          color: red;
        }
      `}

      <p className="intro">CSS-in-JS made simple -- just Style It.</p>
    </Style>
  }
}

export default Intro;
Joshua Robinson
  • 3,430
  • 1
  • 27
  • 35
1

Use Radium!

The following is an example from their website:

var Radium = require('radium');
var React = require('react');
var color = require('color');

@Radium
class Button extends React.Component {
  static propTypes = {
    kind: React.PropTypes.oneOf(['primary', 'warning']).isRequired
  };

  render() {
    // Radium extends the style attribute to accept an array. It will merge
    // the styles in order. We use this feature here to apply the primary
    // or warning styles depending on the value of the `kind` prop. Since its
    // all just JavaScript, you can use whatever logic you want to decide which
    // styles are applied (props, state, context, etc).
    return (
      <button
        style={[
          styles.base,
          styles[this.props.kind]
        ]}>
        {this.props.children}
      </button>
    );
  }
}

// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
  base: {
    color: '#fff',

    // Adding interactive state couldn't be easier! Add a special key to your
    // style object (:hover, :focus, :active, or @media) with the additional rules.
    ':hover': {
      background: color('#0074d9').lighten(0.2).hexString()
    }
  },

  primary: {
    background: '#0074D9'
  },

  warning: {
    background: '#FF4136'
  }
};
fkilaiwi
  • 290
  • 1
  • 8
  • 3
    You're showing how to apply a hover CSS effect using inline styles and not how to reliably run code when the cursor is over the element, which is what the user asked for. – Gunchars Dec 01 '16 at 19:03
  • not sure why my answer is not accepted based on that. – fkilaiwi Dec 13 '16 at 21:54
  • 2
    @Gunchars "*How can you achieve either a hover event or active event in ReactJS when you do inline styling?*". Very first sentence of OP's question. It is pretty much exactly what he asked for. – trixn Jul 15 '17 at 11:37
0

I had a similar issue when onMouseEnter was called but sometimes the corresponding onMouseLeave event wasn't fired, here is a workaround that works well for me (it partially relies on jQuery):

var Hover = React.createClass({
    getInitialState: function() {
        return {
            hover: false
        };
    },
    onMouseEnterHandler: function(e) {
        this.setState({
            hover: true
        });
        console.log('enter');

        $(e.currentTarget).one("mouseleave", function (e) {
            this.onMouseLeaveHandler();
        }.bind(this));

    },
    onMouseLeaveHandler: function() {
        this.setState({
            hover: false
        });
        console.log('leave');
    },
    render: function() {
        var inner = normal;
        if(this.state.hover) {
            inner = hover;
        }

        return (
            <div style={outer}>
                <div style={inner}
                    onMouseEnter={this.onMouseEnterHandler} >
                    {this.props.children}
                </div>
            </div>
        );
    }
});

See on jsfiddle: http://jsfiddle.net/qtbr5cg6/1/


Why was it happening (in my case): I am running a jQuery scrolling animation (through $('#item').animate({ scrollTop: 0 })) when clicking on the item. So the cursor doesn't leave the item "naturally", but during a the JavaScript-driven animation ... and in this case the onMouseLeave was not fired properly by React (React 15.3.0, Chrome 51, Desktop)

kunnix
  • 91
  • 1
  • 5
0

I know It's been a while since this question was asked but I just run into the same issue of inconsistency with onMouseLeave() What I did is to use onMouseOut() for the drop-list and on mouse leave for the whole menu, it is reliable and works every time I've tested it. I saw the events here in the docs: https://facebook.github.io/react/docs/events.html#mouse-events here is an example using https://www.w3schools.com/bootstrap/bootstrap_dropdowns.asp:

handleHoverOff(event){
  //do what ever, for example I use it to collapse the dropdown
  let collapsing = true;
  this.setState({dropDownCollapsed : collapsing });
}

render{
  return(
    <div class="dropdown" onMouseLeave={this.handleHoverOff.bind(this)}>
      <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Dropdown Example
      <span class="caret"></span></button>
      <ul class="dropdown-menu" onMouseOut={this.handleHoverOff.bind(this)}>
        <li><a href="#">bla bla 1</a></li>
        <li><a href="#">bla bla 2</a></li>
        <li><a href="#">bla bla 3</a></li>
      </ul>
    </div>
  )
}
RamiroIsBack
  • 260
  • 1
  • 3
  • 15
0

I personally use Style It for inline-style in React or keep my style separately in a CSS or SASS file...

But if you are really interested doing it inline, look at the library, I share some of the usages below:

In the component:

import React from 'react';
import Style from 'style-it';

class Intro extends React.Component {
  render() {
    return (
      <Style>
        {`
          .intro {
            font-size: 40px;
          }
        `}

        <p className="intro">CSS-in-JS made simple -- just Style It.</p>
      </Style>
    );
  }
}

export default Intro;

Output:

    <p class="intro _scoped-1">
      <style type="text/css">
        ._scoped-1.intro {
          font-size: 40px;
        }
      </style>

      CSS-in-JS made simple -- just Style It.
    </p>


Also you can use JavaScript variables with hover in your CSS as below :

import React from 'react';
import Style from 'style-it';

class Intro extends React.Component {
  render() {
    const fontSize = 13;

    return Style.it(`
      .intro {
        font-size: ${ fontSize }px;  // ES2015 & ES6 Template Literal string interpolation
      }
      .package {
        color: blue;
      }
      .package:hover {
        color: aqua;
      }
    `,
      <p className="intro">CSS-in-JS made simple -- just Style It.</p>
    );
  }
}

export default Intro;

And the result as below:

<p class="intro _scoped-1">
  <style type="text/css">
    ._scoped-1.intro {
      font-size: 13px;
    }
    ._scoped-1 .package {
      color: blue;
    }
    ._scoped-1 .package:hover {
      color: aqua;
    }
  </style>

  CSS-in-JS made simple -- just Style It.
</p>
Alireza
  • 100,211
  • 27
  • 269
  • 172
0

Hover is a CSS feature; it comes with the CSS side of the app so do it the right way is so much fun. Simply put, when I require a hover effect on an element in the React app, first, I create a class in my CSS file and then I add the created class into the className of the element. The steps I follow are:

  1. Create a CSS file unless you have index.css

  2. Create a class with a hover pseudo-class enabled

    .hover__effect:hover {}

  3. Add the effects you require

    .hover__effect:hover { background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); }

  4. Then add the hover__effect to the class's component that should be different when the mouse pointer hovers over it

    Hover

Please check my sandbox for a live demo.

Esmaeil MIRZAEE
  • 1,110
  • 11
  • 14