I'm not sure how to ask this question, because I'm still unable to accurately frame the problem.
I've created a useHover function. Below, you'll see that I am mapping over data and rendering a bunch of photos. However, the useHover only works on the first iteration.
I suspect that it's because of my ref. How does this work? Should I creating a new ref inside of each iteration -- or is that erroneous thinking..?
How can I do this?
Here's my useHover function.
const useHover = () => {
const ref = useRef();
const [hovered, setHovered] = useState(false);
const enter = () => setHovered(true);
const leave = () => setHovered(false);
useEffect(() => {
ref.current.addEventListener("mouseenter", enter);
ref.current.addEventListener("mouseleave", leave);
return () => {
ref.current.removeEventListener("mouseenter", enter);
ref.current.removeEventListener("mouseleave", leave);
};
}, [ref]);
return [ref, hovered];
};
And here's my map function. As you can see I've assigned the ref to the image.
The problem: Only one of the images works when hovered.
const [ref, hovered] = useHover();
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item => (
<div className="row imageSpace">
{hovered && <h1>{item.fields.name}</h1>}
<img
ref={ref}
className="image"
key={item.sys.id}
alt="fall"
src={item.fields.image.file.url}
/>
</div>
))}
</Div>
I'd handle this by using CSS if at all possible, rather than handling hovering in my JavaScript code.
If doing it in JavaScript code, I'd handle this by creating a component for the things that are hovered:
function MyImage({src, header}) {
const [ref, hovered] = useHover();
return (
<div className="row imageSpace">
{hovered && <h1>{header}</h1>}
<img
ref={ref}
className="image"
alt="fall"
src={src}
/>
</div>
);
}
and then use that component:
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item =>
<MyImage
key={item.sys.id}
src={item.fields.image.file.url}
header={item.fields.name}
/>
)}
</Div>
(Obviously, make more of the props configurable if you like.)
As a general rule when you have a parent item with Array.map()
, and functionality for each array item, refactor the items to a separate component (ImageRow
in my code).
In this case you don't need to use refs for event handling, since React can handle that for you. Instead of return a ref from useHover
, return an object with event handlers, and spread it on the component.
const { useState, useMemo } = React;
const useHover = () => {
const [hovered, setHovered] = useState(false);
const eventHandlers = useMemo(() => ({
onMouseEnter: () => setHovered(true),
onMouseLeave: () => setHovered(false)
}), [setHovered]);
return [hovered, eventHandlers];
};
const ImageRow = ({ name, url }) => {
const [hovered, eventHandlers] = useHover();
return (
<div className="row imageSpace">
{hovered && <h1>{name}</h1>}
<img
className="image"
alt="fall"
src={url}
{...eventHandlers}
/>
</div>
);
};
const images = [{ id: 1, name: 'random1', url: 'https://picsum.photos/200?1' }, { id: 2, name: 'random2', url: 'https://picsum.photos/200?2' }, { id: 3, name: 'random3', url: 'https://picsum.photos/200?3' }];
const Wrapper = ({ images }) => (
<div style={{ display: 'flex' }}>
{images.map(({ id, ...props }) => <ImageRow key={id} {...props} />)}
</div>
);
ReactDOM.render(
<Wrapper images={images} />,
root
);
h1 {
position: absolute;
pointer-events: none;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>