React styles that hover

2015-07-14

How to enable hovering using inline styles

React introduces a number of interesting ideas, one of which is that you can inline your styles instead of using external CSS. However, there are certain properties that can't be inlined with CSS alone.

JavaScript to the rescue

The solution is straightforward; change the style based on mouse events. Let's create a basic label component that changes color when the mouse hovers over it.

var Label = React.createClass({

    getInitialState : function() {
      return { hovered: false }
    },

    style: function() {
      if (this.state.hovered) {
        return { backgroundColor: "red" }
      } else {
        return { backgroundColor: "grey" }
      }
    },

    onMouseOver : function () {
      this.setState({ hovered:true });
    },

    onMouseOut : function () {
      this.setState({ hovered:false });
    },

    render: function() {
      return <span onMouseOver={this.onMouseOver} 
                   onMouseOut={this.onMouseOut} 
                   style={this.style()}>Hover me</span>
    }
});

Cleaning up with indirection

Having to add mouse event handlers in every component can quickly become tedious. Let's introduce a level of indirection, our very own hover mixin.

The idea is to have the mixin encapsulate the hover functionality. This includes the hover state and the mouse listeners.

var HoverMixin = {

    // Initial state
    componentWillMount: function() {
      this.state = { hovered: false };
    },
    
    // Attach mouse listeners to the root node of the component
    componentDidMount: function() {
      this.getDOMNode().addEventListener("mouseover", this.onMouseOver);
      this.getDOMNode().addEventListener("mouseout", this.onMouseOut);
    },
    
    // Clean up listeners when the component unmounts
    componentWillUnmount: function() {
      this.getDOMNode().removeEventListener("mouseover", this.onMouseOver);
      this.getDOMNode().removeEventListener("mouseout", this.onMouseOut);
    },
    
    // Mutate state
    onMouseOver: function() {
      this.setState({ hovered: true });
    },
    
    onMouseOut: function() {
      this.setState({ hovered: false });
    }
  };

Let's clean up the Label component by hooking up the HoverMixin:

var Label = React.createClass({
    
    // Apply the mixin 
    mixins: [HoverMixin],

    style: function() {
      // this.state.hovered comes from the mixin
      if (this.state.hovered) {
        return { backgroundColor: "red" }
      } else {
        return { backgroundColor: "grey" }
      }
    },

    render: function() {
      return <span onMouseOver={this.onMouseOver} 
                   onMouseOut={this.onMouseOut} 
                   style={this.style()}>Hover me</span>
    }
});

Mixins are great?

In general, I prefer code that spells out and does what it says. Using mixins can be adverserial to readability; all abstractions are leaky.

CVTwitterLinkedinstackoverflowKodemakerFlickr500pxRSS