1

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>
4
  • It seems like it would. Not sure how to implement this though. Commented Dec 10, 2019 at 19:09
  • 1
    You just need to use useHover in the iterator within separate component. @T.J. Crowder answer would solve your problem Commented Dec 10, 2019 at 19:17
  • @Dupocas - And agreed, this is a duplicate of that. Sadly that has no upvoted or accepted answers...yet. :-) Commented Dec 10, 2019 at 19:18
  • Seems a little selfish to point to my own answer right? hahaha And yours seems way more specific. I'm deleting the comment and upvoting yours Commented Dec 10, 2019 at 19:23

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

4 Comments

This is great. Feels close. But the image component doesn't have access to item, and I'm receiving the error "'item' is not defined".
@WesleyBonneville - I just missed out a prop it needs, somehow I didn't see it in the h1...
@WesleyBonneville - I've fixed it.
Nailed it! I really like this solution. Thanks a lot, it works great.
0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.