Search code examples
reactjsreact-hooksnext.jsstyled-components

Changing image source on mouseover/mouseout in React/Next.js


Technologies I use:

Next.js Styled-Components Typescript

What I want to do:

I wish to change image to a different one when I hover over the parent div that wraps it (Card).

What is my solution:

I managed to do that with use of useState like so:

const [isHovering, setIsHovered] = useState(false);
const [isHovering2, setIsHovered2] = useState(false);
const [isHovering3, setIsHovered3] = useState(false);


  

      <Card1>
        <Card
          onMouseEnter={() => setIsHovered(true)}
          onMouseLeave={() => setIsHovered(false)}
        >
          <IconWrapper>
            {isHovering ? (
              <Image layout="fill" src={image1OnHover} />
            ) : (
              <Image layout="fill" src={image1} />
            )}
          </IconWrapper>
          <CardText>
            <h4>Title</h4>
            <p>
              Dorem Ipsum.
            </p>
          </CardText>
        </Card>
      </Card1>

      <Card2>
        <Card
          onMouseEnter={() => setIsHovered2(true)}
          onMouseLeave={() => setIsHovered2(false)}
        >
          <IconWrapper>
            {isHovering2 ? (
              <Image layout="fill" src={image2OnHover} />
            ) : (
              <Image layout="fill" src={image2} />
            )}
          </IconWrapper>
          <CardText>
            <h4>Title</h4>
            <p>
              Dorem Ipsum
            </p>
          </CardText>
        </Card>
      </Card2>

      <Card3>
        <Card
          onMouseEnter={() => setIsHovered3(true)}
          onMouseLeave={() => setIsHovered3(false)}
        >
          <IconWrapper>
            {isHovering3 ? (
              <Image layout="fill" src={image3OnHover} />
            ) : (
              <Image layout="fill" src={image3} />
            )}
          </IconWrapper>
          <CardText>
            <h4>Title</h4>
            <p>
              Dorem Ipsum
            </p>
          </CardText>
        </Card>

What I'd like to ask for:

It seems to me like this code looks awful and is not really following the DRY principle.

It works but I don't think that spamming useStates is a good idea (what if there were 100 images to change... that would be pain to write).

I've been searching web for some better ideas, but I only found examples similar to what I've coded and for singular images. The problem here is that if I was to only use one state, then all of the images would change at the same time - which forces me to use individual hooks for each and every element I add to the page.

This is my first ever request on stackoverflow - I apologize if my post is not up to standards - hopefully it'll do ;)


Solution

  • Another Option is to make a component that handles the state itself then pass the two image options into it. Here is an example of a simple component that does that: https://codesandbox.io/s/hoverimage-ldscek?file=/src/OnHoverImage.js:40-449. Then you could map that component and display that component as many times as you like easily.

    export const OnHoverImage = ({ hoveredImage, image, alt }) => {
      const [hover, setHover] = useState(false);
      return (
        <div
          style={{ width: "200px", height: "200px" }}
          onMouseEnter={() => setHover(true)}
          onMouseLeave={() => setHover(false)}
        >
          {hover ? (
            <img src={image} alt={alt} />
          ) : (
            <img src={hoveredImage} alt={alt} />
          )}
        </div>
      );
    };
    

    Mapping the image would look like this:

    <div>
        {arrayOfImages.map((el) => (
                <OnHoverImage
                  alt={el.alt}
                  hoveredImage={el.hoverImage}
                  image={el.image}
                />
              ))}
    </div>