How to change image on hover with NextJS and TailwindCSS?

Viewed 7361

I am trying to get a Logo that utilizes React.forwardRef and NextJS Link to change to a different image on hover and still work.

This was fairly straightforward in CSS, but I'm stuck on how to do this in the NextJS / Tailwind world.

Currently I'm getting by with a hover: animate-pulse at the moment...

Help appreciated!

import React from "react";
import Link from "next/link";
import Image from "next/image";

const MyLogo = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      <Image src="/logo1.png" width={88} height={77} alt="logo" />
    </a>
  );
});

export default function Nav() {
  return (
    <nav className="flex items-center justify-between flex-wrap bg-raisin-black p-6">
      <div className="flex items-center flex-shrink-0 mr-6 cursor-pointer hover:animate-pulse">
        <Link href="/">
          <MyLogo />
        </Link>
      </div>
    </nav>
  );
}

2 Answers

Just some extra info for you and your current implementation

Currently, your nav re-renders on every logo image hover. Moving the logo component out of your nav will stop your whole nav component from re-rendering on every hover because of the state change.

In your case, you do not need forward ref - I left forward ref in the example because you can technically make the link component a global reusable component.

You're losing the ability to set most of the available next/link prop options on your custom next/link.

Here is a slightly modified version that addresses these concerns.

import React, { useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';

const MyLink = React.forwardRef(
  (
    { as, children, href, replace, scroll, shallow, passHref, ...rest }, // extract all next/link props and pass the rest to the anchor tag
    ref,
  ) => (
    <Link as={as} href={href} passHref={passHref} replace={replace}>
      <a {...rest} ref={ref}>
        {children}
      </a>
    </Link>
  ),
);

const Logo = () => {
  const [isHovering, setIsHovered] = useState(false);
  const onMouseEnter = () => setIsHovered(true);
  const onMouseLeave = () => setIsHovered(false);
  return (
    <div
      className="flex items-center flex-shrink-0 mr-6 cursor-pointer"
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
    >
      <MyLink href="/">
        {isHovering ? (
          <Image src="/logo4.png" width={88} height={77} alt="logo" />
        ) : (
          <Image src="/logo1.png" width={88} height={77} alt="logo" />
        )}
      </MyLink>
    </div>
  );
};

export default function Nav() {
  return (
    <nav className="flex items-center justify-between flex-wrap bg-raisin-black p-6">
      <Logo />
    </nav>
  );
}

My solution thanks to Sean W's suggested reading:

import React, { useState } from "react";
import Link from "next/link";
import Image from "next/image";

const MyLogo = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      <Image src="/logo1.png" width={88} height={77} alt="logo" />
    </a>
  );
});

const MyLogoAlt = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      <Image src="/logo4.png" width={88} height={77} alt="logo" />
    </a>
  );
});

export default function Nav() {
const [isShown, setIsShown] = useState(false);

  return (
    <nav className="flex items-center justify-between flex-wrap bg-raisin-black p-6">
      <div
        className="flex items-center flex-shrink-0 mr-6 cursor-pointer"
        onMouseEnter={() => setIsShown(true)}
        onMouseLeave={() => setIsShown(false)}
      >
        {isShown && (
          <Link href="/">
            <MyLogoAlt />
          </Link>
        )}
        {!isShown && (
          <Link href="/">
            <MyLogo />
          </Link>
        )}
      </div>
    </nav>
  );
}
Related