0

This question stems from my attempts to create a decorator-type component that adds functionality to dumb presentation components by wrapping them and injecting the desired styles and/or behavior.

Here is an example of a "hover decorator" I would like to create:

class HoverDecorator extends React.Component {
    constructor(props) {
        super(props);

        let defaultState = { hover: false };
        this.state = Object.assign(defaultState, props);
    }

    handleMouseOver(event) {
        console.log('Mouse Over');
        this.setState({
            hover: true
        });
    }
    handleMouseOut(event) {
        console.log('Mouse Out');
        this.setState({
            hover: false
        });
    }

    render() {
        let hoverStyle = {};
        if (this.state.hover) { // Change the color if hovered.
            hoverStyle = { color: '#FF0000' };
        }

        // Inject the new style and event handlers into the child component.
        return React.cloneElement(
            React.Children.only(this.state.children), // Only allows a single child.
            {
                style: hoverStyle,
                onMouseOver: this.handleMouseOver.bind(this),
                onMouseOut: this.handleMouseOut.bind(this)
            }
        );
    }
}

Which could be used on a dummy component like this:

class DummyTextBox extends React.Component {
    constructor(props) {
        super(props);
        this.state = props;
    }

    render() {
        let boxStyle = { color: '#0000FF' };
        // Overwrite the default boxStyle with any styles passed in via props.
        let mergedStyle = Object.assign(boxStyle, this.state.style);

        return (<span style={mergedStyle}>{this.state.children}</span>);
    }
}

The JSX used to create and wrap the DummyTextBox would look like:

<HoverDecorator>
    <DummyTextBox>Lorem Ipsum</DummyTextBox>
</HoverDecorator>

My problem is that the above code will add the onMouseOver and onMouseOut event listeners to the DummyTextBox virtual DOM element, and not the actual span that it renders. When I inspect the React DOM through the chrome extension, I see the following:

<HoverDecorator>
    <DummyTextBox style={} onMouseOver=bound handleMouseOver() onMouseOut=bound handleMouseOut()>
        <span style={color: "#0000FF"}>Lorem Ipsum</span>
    </DummyTextBox>
</HoverDecorator>

This of course will not work, because DummyTextBox is just a virtual DOM element by itself. Is there any way that I can add event listeners to the <span> that is returned by the DummyTextBox's render() method?

Christopher Sheaf
  • 185
  • 1
  • 3
  • 13

1 Answers1

0

Have you tried using an ref tag on the span you're trying to add event listeners to?

<span 
ref={el => {this.decorator = el}} 
style={mergedStyle}>
  {this.state.children}
</span>

Follow up question: Why are you cloning? What does that accomplish that regular rendering can't?

edit: Wow, so sorry. I meant ref, not href.

Andrew
  • 7,201
  • 5
  • 25
  • 34
  • I would like for the `HoverDecorator` to be able to wrap any generic React component without having to modify that component's source. It might wrap a `span`, or a `div`, or a `li`, or anything. I'm cloning to merge the new props into the child's existing props, as shown [here](https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children?rq=1). I'm not familiar with what you're demonstrating using the `href` tag; I've never seen it used outside of a web address link. – Christopher Sheaf Sep 28 '17 at 02:16
  • `href` in that context is used to have very specific/direct access to that element. Think of it like a `getElementById` or any other query selector. You now have `this.dectorator` to play around with and manipulate however you want inside your component. – Andrew Sep 28 '17 at 04:16
  • @ChristopherSheaf I updated my answer. I meant `ref`, but the concept is still the same. – Andrew Sep 28 '17 at 04:32
  • So, if I'm understanding the usage of `ref` correctly, it will give me a reference to the `` which can then be stored as an instance variable of the `DummyTextBox` class? Is there a way to use `ref` from the scope of the `HoverDecorator` class via `props.children` instead? My goal is to write a decorator that can work on components that are not specifically modified to be compatible with the decorator. – Christopher Sheaf Sep 28 '17 at 05:53
  • @ChristopherSheaf Regarding the `ref`, yes that is correct. Have you tried lifecycle methods like `componentDidMount`? Here's a possible idea: In your `HoverDecorator`, create a function that you pass down to the child (but refers back to itself if run). In that, use `componentDidMount` and pass that `ref` to that function, which may be able to give `HoverDecorator` that `ref`. That kind of changes your intended coding style, but it might be able to solve your problem. – Andrew Sep 28 '17 at 06:08