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)
withconsole.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