DEV Community

Rui Teixeira
Rui Teixeira

Posted on

Hoverable Component with Render Props

This week I had to implement some onHover behavior for a component but wanted to make it reusable and not have it as part of my component's state.

I have only been developing with React full time for the last 6 months so was struggling to come up with a clean way to do this.

Luckily I also started watching @kentcdodds 's Advanced React Patterns where he explains how you can achieve this using render props.

So I implemented Hoverable with a render prop (children). I can then pass it my existing component which takes in a prop hovered and I can easily reuse it for other components.

Demo of usage in CodeSandbox

Below is an example of how I implemented. You can also find it in this CodeSandbox

With Typescript I wasn't able to use children as the prop so I used a renderprop... literally! You can see that in this CodeSandbox

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class Hoverable extends React.Component {
    state = { hovered: false };
    render() {
        return (
            <div
                onMouseEnter={() => this.setState({ hovered: true })}
                onMouseLeave={() => this.setState({ hovered: false })}
            >
                {this.props.children(this.state.hovered)}
            </div>
        );
    }
}

function App() {
    return (
        <Hoverable>
            {hovered => <div>{hovered ?  "🔥" : "🦄"}</div>}
        </Hoverable>
    );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Enter fullscreen mode Exit fullscreen mode

Top comments (8)

Collapse
 
patroza profile image
Patrick Roza • Edited

With typescript you could change the definition of the children prop type, or cast props to any before accessing children.

I would suggest to move the mouseEnter and Leave call backs to properties of the class so that you dont re create the functions every render pass

Collapse
 
ruiclarateixeira profile image
Rui Teixeira • Edited

Thanks for the tips!

For the ts children definition - what would it look like? The code below still complains that children is not a function. CodeSandbox

interface HoverableProps {
  children(hovered: boolean): React.ReactNode;
}

class Hoverable extends React.Component<HoverableProps> {
  state = { hovered: false };
  render() {
    return (
      <div
        onMouseEnter={() => this.setState({ hovered: true })}
        onMouseLeave={() => this.setState({ hovered: false })}
      >
        {this.props.children(this.state.hovered)}
      </div>
    );
  }
}
Collapse
 
ruiclarateixeira profile image
Rui Teixeira

Actually once I updated the usage to use children there's no longer errors.

    <Hoverable>{hovered => <div>{hovered ? "🔥" : "🦄"}</div>}</Hoverable>

That's awesome! thanks!

Thread Thread
 
patroza profile image
Patrick Roza

Sure, no worries.
I think the more correct would be:
children: (hovered: boolean) => void

As this implies a property with function signature instead of a method.

Thread Thread
 
ruiclarateixeira profile image
Rui Teixeira

What is the difference between having it as a method or a property with function signature?

Collapse
 
nicolasletoublon profile image
Nicolas Letoublon

I have the same implementation.
And I encounter many issues whenever you are going super fast between many elements. It's like the state gets lost, and I saw that going that fast, many onMouseEnter / onMouseLeave gets forgotten, so the hovered state stays even if you are elsewhere...
Do you have tried that ?

Collapse
 
dance2die profile image
Sung M. Kim

Best emojis to use for the hover effect.

Burning 🔥 a 🦄 on hover :)

Collapse
 
ruiclarateixeira profile image
Rui Teixeira

Ah! Did not mean for to be so brutal!