Home > Blockchain >  mouseEnter and mouseLeave work on HTML elements, but not React components?
mouseEnter and mouseLeave work on HTML elements, but not React components?

Time:10-09

First time making a React site (using Gatsby).

What I want to happen

On my index page, I'm trying make a Note component appear when the mouse hovers over a Term component.

What is happening

When I add onMouseEnter and onMouseLeave directly to the Term component, the Note never appears. However, if I use a native html element (span in example code below) instead of <Term/>, the Note does appear.

Does it have something to do with the arrow function?

  • Replacing () => setShowNoteB(true) with console.log("in TermB") (sort of) works.
  • But using onMouseEnter={setShowNoteB(true)} results in an infinite loop error.

Is it related to how I'm composing the components? or is a it a limitation of the useState hook? What I'm doing here seems fairly simple/straightforward, but again, I'm new to this, so maybe there's some rule that I don't know about.

Example code

Index page

//index.js

import * as React from 'react';
import { useState } from 'react';
import Term from '../components/term';
import Note from '../components/note';


const IndexPage = () => {
  const [showNoteA, setShowNoteA] = useState(false);
  const [showNoteB, setShowNoteB] = useState(false);

  return (
    <>
      <h1
      >
        <span
          onMouseEnter={() => setShowNoteA(true)}
          onm ouseLeave={() => setShowNoteA(false)}
        > TermA* </span>
        {" "} doesn't behave like {" "}
        <Term word="TermB" symbol="†"
          onMouseEnter={() => setShowNoteB(true)}
          onm ouseLeave={() => setShowNoteB(false)}
        />
      </h1>

      {showNoteA ? <Note> <p>This is a note for TermA.</p> </Note> : null}
      {showNoteB ? <Note> <p>This is a note for TermB.</p> </Note> : null}
    </>
  );
};

export default IndexPage

Term component

// term.js

import React from 'react';

const Term = ({ word, symbol }) => {

  return (
    <div>
      <span>{word}</span>
      <span>{symbol}</span>
    </div>
  );
};

export default Term;

Note component

// note.js

import * as React from 'react';

const Note = ({ children })=> {
  return (
    <div>
      {children}
    </div>
  )
};

export default Note;

CodePudding user response:

When you use Components in your JSX, as opposed to (lowercase) HTML tags, any attribute is only passed as prop and has no further effect directly.

<Term
  word="TermB" symbol="†"
  onm ouseEnter={() => setShowNoteB(true)}
  onm ouseLeave={() => setShowNoteB(false)}
/>

You need to grab the passed prop and assign it to an actual HTML element in the Component's JSX, like this:

const Term = ({ word, symbol, onm ouseEnter, onm ouseLeave }) => {
  return (
    <div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
      <span>{word}</span> <span>{symbol}</span>
    </div>
  );
};

CodePudding user response:

Solution with useEffect, useRef, forwardRef and addEventListener/removeEventListener:

App.js

import React, { useState, useRef, useEffect } from 'react';
import './style.css';

import Term from './Term';
import Note from './Note';

export default function App() {
  const [isHovered, setHovered] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current) {
      ref.current.addEventListener('mouseover', handleMouseover);
      ref.current.addEventListener('mouseout', handleMouseout);

      return () => {
        ref.current.removeEventListener('mouseover', handleMouseover);
        ref.current.removeEventListener('mouseout', handleMouseout);
      };
    }
  }, [ref.current]);

  const handleMouseover = () => setHovered(true);
  const handleMouseout = () => setHovered(false);

  return (
    <>
      <div>
        Lorem ipsum dolor sit amet, <Term ref={ref} word="consectetur" />{' '}
        adipiscing elit. Morbi laoreet lacus in dui vestibulum, nec imperdiet
        augue vulputate.
      </div>
      {isHovered ? <Note word={ref.current.innerHTML} /> : null}
    </>
  );
}

Term.js

import React, { forwardRef } from 'react';

function Term({ word }, ref) {
  return (
    <span ref={ref} className="term">
      {word}
    </span>
  );
}

const forwarded = forwardRef(Term);
export default forwarded;

Note.js

import React from 'react';

export default function Note({ word }) {
  return <span className="note">This is a note for {word}</span>;
}

Demo: Stackblitz

  • Related