Search code examples
javascriptreactjsuse-statemouseenter

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


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)}
          onMouseLeave={() => setShowNoteA(false)}
        > TermA* </span>
        {" "} doesn't behave like {" "}
        <Term word="TermB" symbol="†"
          onMouseEnter={() => setShowNoteB(true)}
          onMouseLeave={() => 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;

Solution

  • 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="†"
      onMouseEnter={() => setShowNoteB(true)}
      onMouseLeave={() => 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, onMouseEnter, onMouseLeave }) => {
      return (
        <div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
          <span>{word}</span> <span>{symbol}</span>
        </div>
      );
    };