neutrino
neutrino

Reputation: 924

Render unique divs for each hovered element

minimum reproducible example: https://codesandbox.io/s/react-hover-example-tu1eu?file=/index.js

I currently have a new element being rendered when either of 2 other elements are hovered over. But i would like to render different things based upon which element is hovered.

In the example below and in the codepen, there are 2 hoverable divs that are rendered; when they are hovered over, it changes the state and another div is rendered. I would like for the HoverMe2 div to render text "hello2". Currently, whether i hover hoverme1 or 2, they both just render the text "hello".

import React, { Component } from "react";
import { render } from "react-dom";

class HoverExample extends Component {
  constructor(props) {
    super(props);
    this.handleMouseHover = this.handleMouseHover.bind(this);
    this.state = {
      isHovering: false
    };
  }

  handleMouseHover() {
    this.setState(this.toggleHoverState);
  }

  toggleHoverState(state) {
    return {
      isHovering: !state.isHovering
    };
  }

  render() {
    return (
      <div>
        <div
          onMouseEnter={this.handleMouseHover}
          onMouseLeave={this.handleMouseHover}
        >
          Hover Me
        </div>
        <div
          onMouseEnter={this.handleMouseHover}
          onMouseLeave={this.handleMouseHover}
        >
          Hover Me2
        </div>
        {this.state.isHovering && <div>hello</div>}
      </div>
    );
  }
}

render(<HoverExample />, document.getElementById("root"));

Upvotes: 1

Views: 87

Answers (4)

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

If the whole point is about linking dynamically messages to JSX-element you're hovering, you may store that binding (e.g. within an object).

Upon rendering, you simply pass some anchor (e.g. id property of corresponding object) within a custom attribute (data-*), so that later on you may retrieve that, look up for the matching object, put linked message into state and render the message.

Following is a quick demo:

const { Component } = React,
      { render } = ReactDOM,
      rootNode = document.getElementById('root')
      
const data = [
  {id:0, text: 'Hover me', message: 'Thanks for hovering'},
  {id:1, text: 'Hover me too', message: 'Great job'}
]      
      
class HoverableDivs extends Component {
  state = {
    messageToShow: null
  }
  
  enterHandler = ({target:{dataset:{id:recordId}}}) => {
    const  {message} = this.props.data.find(({id}) => id == recordId)
    this.setState({messageToShow: message})
  }
  
  leaveHandler = () => this.setState({messageToShow: null})
  
  render(){
    return (
      <div>
        {
          this.props.data.map(({text,id}) => (
            <div 
              key={id}
              data-id={id}
              onMouseEnter={this.enterHandler}
              onMouseLeave={this.leaveHandler}
            >
              {text}
            </div>
          ))
        }
        {
          this.state.messageToShow && <div>{this.state.messageToShow}</div>
        }
      </div>
    )
  }
}   

render (
  <HoverableDivs {...{data}} />,
  rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Upvotes: 1

KushalSeth
KushalSeth

Reputation: 4669

You can pass the context text as shown in example. This is working code:

import React, { Component } from "react";
import { render } from "react-dom";


// Drive this using some configuration. You can set based on your requirement.
export const HOVER_Hello1 = "Hello1";
export const HOVER_Hello2 = "Hello2";

class HoverExample extends Component {


    constructor(props) {
        super(props);
        this.handleMouseHover = this.handleMouseHover.bind(this);
        this.state = {
            isHovering: false,
            contextText: ""
        };
    }

    handleMouseHover = (e, currentText) => {
        this.setState({
            isHovering: !this.state.isHovering,
            contextText: currentText
        });
    }

    toggleHoverState(state) {
        //
    }

    render() {
        return (
            <div>
                <div
                    onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello1)}
                    onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello1)}

                >
                    Hover Me
        </div>
                <div
                    onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello2)}
                    onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello2)}
                >
                    Hover Me2
        </div>
                {this.state.isHovering && <div>{this.state.contextText}</div>}
            </div>
        );
    }
}

export default HoverExample;

Upvotes: 1

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11283

You need to keep the state of item which you have hovered that's for sure

const { Component, useState, useEffect } = React;

class HoverExample extends Component {
  constructor(props) {
    super(props);
    this.handleMouseHover = this.handleMouseHover.bind(this);
    this.state = {
      isHovering: false,
      values: ['hello', 'hello2'],
      value: 'hello'
    };
  }

  handleMouseHover({target: {dataset: {id}}}) {
    this.setState(state => {
      return {
        ...state,
        isHovering: !state.isHovering,
        value: state.values[id]
      };
    });
  }

  render() {
    return (
      <div>
        <div
          data-id="0"
          onMouseEnter={this.handleMouseHover}
          onMouseLeave={this.handleMouseHover}
        >
          Hover Me
        </div>
        <div
          data-id="1"
          onMouseEnter={this.handleMouseHover}
          onMouseLeave={this.handleMouseHover}
        >
          Hover Me2
        </div>
        {this.state.isHovering && <div>{this.state.value}</div>}
      </div>
    );
  }
}

ReactDOM.render(
    <HoverExample />,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

Upvotes: 2

Marko Gresak
Marko Gresak

Reputation: 8217

As @CevaComic pointed out, you can do this with CSS. But if you want to use React, for example, because your actual problem is more complex, here is the answer.

You will need a way to tell apart the two elements. It could be done with some neat tricks, like setting an unique id to each element, passing a custom argument, or something else.

But I would advise against "cool tricks" as it's more difficult to understand what is going on, and the code is more prone to errors. I think the best way it to use a dumb approach of unique functions for unique elements.

Each onMouseEnter and onMouseLeave has to be an unique function (e.g. handleMouseHover1 and handleMouseHover2), and each of those functions need to control unique state (for example, isHovering1 and isHovering2). Then you have to render the element you want based on the state. Of course, for a real-world code, you will probably want to use more descriptive names to make the code more comprehensible. The full code would look something like this.

class HoverExample extends Component {
  state = {
    isHovering1: false,
    isHovering2: false
  };

  handleMouseHover1 = () => {
    this.setState(({ isHovering1 }) => ({ isHovering1: !isHovering1 }));
  };

  handleMouseHover2 = () => {
    this.setState(({ isHovering2 }) => ({ isHovering2: !isHovering2 }));
  };

  render() {
    const { isHovering1, isHovering2 } = this.state;

    return (
      <div>
        <div
          onMouseEnter={this.handleMouseHover1}
          onMouseLeave={this.handleMouseHover1}
        >
          Hover Me1
        </div>
        <div
          onMouseEnter={this.handleMouseHover2}
          onMouseLeave={this.handleMouseHover2}
        >
          Hover Me2
        </div>
        {isHovering1 && <div>hello1</div>}
        {isHovering2 && <div>hello2</div>}
      </div>
    );
  }
}

Also, updated example: https://codesandbox.io/s/react-hover-example-rc3h0

Note: I have also edited the code to add some syntax sugar which exists with newer ECMAScript versions. Instead of binding the function, you can use the arrow function format, e.g. fn = () => { ... }. The arrow function means the this context is automatically bound to the function, so you don't have to do it manually. Also, you don't have to initialize this.state inside the constructor, you can define it as a class instance property. With those two things together, you do not need the constructor at all, and it makes the code a bit cleaner.

Upvotes: 0

Related Questions