next.js: Image Component doesn't forwardsRef

Bug report

Describe the bug

Refs aren’t properly supported in Image.

import React, { useState, useCallback } from 'react'
import Image from 'next/image'

const NextImage = ({ src }) => {
    const [width, setWidth] = useState(0)
    const [height, setHeight] = useState(0)

    const ref = useCallback((node) => {
        console.log(node)
    }, [])

    return <Image ref={ref} src={src} width={width} height={height} />
}

This code shows that Image does not forwardRef to img DOM element.

To Reproduce

  1. Add Image component to your page
  2. Add ref via useCallback hook
  3. Try to do something within callback function

Expected behavior

Image component should pass ref function to img element.

System information

  • OS: macOS
  • Browser (if applies) [Chrome, Safari]
  • Version of Next.js: 10.0.0
  • Version of Node.js: 15.0.0

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 55
  • Comments: 33 (11 by maintainers)

Commits related to this issue

Most upvoted comments

For extra context, the Image component exposes an onLoad event but adding onLoad does not reliably work for server side rendered images, the solution is checking img.complete on the ref instead. Not having a ref for the Image component prevents us from doing that kind of check for cases like rendering a skeleton while an image is loading.

Hi everyone, thanks for the feedback!

I read through all the comments here and it seems the majority use cases for ref could be solved by adding a new prop onLoadingComplete().

Does that sounds like a good solution? 👍 or 👎

Echoing @Xetera, a reliable way to detect the load state of an image is required in order to animate its entrance with react-spring, so I would expect access to the img element.

@Timer I would also love for this to forward refs, there are many reasons why this is important, as listed above. The ref should go to the underlying img tag in my mind.

Thanks!

+1 on wanting to pass ref down to to be able to access “imgRef.current.complete”.

I want to use the next/image component for the Automatic Image Optimization, but I want to trigger an animation when the image is loaded.

Does anyone have a suggestion about workaround in the meantime?

My use case: query image natural size, animate it using DOM API.

Other use cases already mentioned by others:

  1. Query element size.
  2. Use with other animation library that’s not React specific or use DOM API for performance reason.
  3. Draw it onto canvas.

We are currently using ‘react-content-loader’ as our skeleton/animation lib, and this is how we handled not having access to the next image ref.

We are basically setting the image as imgLoaded, when it’s not hidden anymore (by the visibility property in the element’s style attribute). It was the best we could came up, at least in terms of syncing when the image was ready to be showed to the user and removing the loader.

const ExampleImage = (): ReactElement => {
  const [imgLoaded, setImgLoaded] = useState(false);

  const handleImageLoad = (event: React.SyntheticEvent<HTMLImageElement, Event>): void => {
    const target = event.target as HTMLImageElement;
    if (target.complete && target.style.visibility !== 'hidden') {
      setImgLoaded(true);
    }
  };

  return (
    <Box>
      {!imgLoaded && <Loader />}
      <Box height={imgHeight}>
        <Image src={imgSrc} onLoad={handleImageLoad} alt={imgAlt} layout="fill" />
      </Box>
    </Box>
  );
};

Would you expect this to go to the top-level wrapper or the <img /> component? Could you please explain the use-case and what you’re trying to do?

I would expect it to go to the component similarly to all other props not referenced by next/image.

My use case is wanting to programatically determine if the image has loaded yet, and toggle a className based on that.

I also support being able to forward a ref to the root image element. In my case I need it for a feature I am building out with framer-motion and need access to the image’s complete property.

I’ve opened up PR #22482 that should address this issue.

I would also prefer the ref approach, my use case is doing animations that will require me to have access to the image element. I’m not sure what the implications of adding the option to forwardRef’s would be, as I’ve not looked into how the Image component works exactly, but it would be nice to have that option enabled for whoever wants to use it.

We released onLoadingComplete() today in next@11.0.2-canary.4.

Try it out now with yarn add next@canary, thanks!

@pffigueiredo There is also a different solution to knowing when the image is loaded. You can avoid looking for the 1x1 gif file so the onLoad event is only fired on the actual image.

const ExampleImage = () => {
  const [imgLoaded, setImgLoaded] = useState(false);

  return (
    <Image
      width={100}
      height={100}
      onLoad={event => {
        // next/image use an 1x1 px git as placeholder. We only want the onLoad event on the actual image
        if (event.target.src.indexOf('data:image/gif;base64') < 0) {
          setImgLoaded(true)
        }
      }}
    />
  );
};

How would one go about using any kind of animation library (say framer motion in my case) with the Image component if framer can’t attach it’s ref onto the container?

There should be affordance to add a user defined ref on the image as well as the container div.

Also currently I don’t think this component is usable with framer even if ref is added because the layout prop collides with framer motion’s layout prop.

My use-case is adding a link to the image, the Link passes ref to it’s child component.

import Image from 'next/image';
import Link from 'next/link';

<Link href="/">
  <Image src="logo.png" />
</Link>

@luigi-derson This looks like a bug, no need for ref.

This will be fixed in PR #32623, thanks!

I’m building an Image Component based that on Next/Image. I want it set width and height based on it’s parent. I had this working on classic img.

You do not need a ref for this behavior. The Image component already works like this with its intrinsic behavior.