Next.js Discord

Discord Forum

Updating dimensions of asynchronously loaded Next Image in ResizeObserver callback

Answered
Californian posted this in #help-forum
Open in Discord
CalifornianOP
Hello,

In one of my components, I have a Next Image loaded with static dimensions, let's say 500 x 500 (let's call it staticImage). I also have an array of images with dimensions loaded asynchronously through an useEffect hook (let's call it dynamicImages).
dynamicImages are in an absolute div for the purpose of one of my features, so when the screen resizes, they don't receive dimension updates like staticImage (which is in a relative div).

- Feature needed: whenever I observe changes in the dimensions of staticImage (due to screen resizes, for example), I want to scale the dimensions of dynamicImages accordingly.

- Current idea: I'm using ResizeObserver API to observe dimension changes in staticImage and, whenever there is one, I scale dynamicImages accordingly.

- Problem: Whenever there is a change in staticImage dimensions, thus triggering the observer callback, dynamicImages aren't still loaded (remember, they are loaded asynchronously) and so I can't scale them at that moment.

- Previous try: bind dynamicImages to the useEffect hook that triggers the ResizeObserver, like the solution for my previous question (https://nextjs-forum.com/post/1190463330360500366). Unlike in the other case, this doesn't work here, because now updates to dynamicImages are done in the same useEffect hook, causing an infinite loop.

Any idea on how to solve this ?

Thanks in advance
Answered by Californian
Solved by starting the observer right after const dynamicImagesData = await imagesLoader(...); instead of in a separate useEffect hook, and iterating directly through dynamicImagesData instead dynamicImages (which comes asynchronously in a flow only controlled by React). Duh, what a stupid me 😕
View full answer

2 Replies

CalifornianOP
Here is an illustrative code for the situation, with all the relevant parts:

  const staticImageRef = useRef<HTMLImageElement>(null);
  const [dynamicImages, setDynamicImages] = useState<ImageInterface[]>([]);

  const updateUploadedImageDimensions = (index: number, width: number, height: number) => {
     setDynamicImages((prevImages) => {
       const newImages = [...prevImages];
       newImages[index].width = width;
       newImages[index].height = height;
      return newImages;
    });
  };
  
  useEffect(() => {
    const dynamicImagesData = await imagesLoader(...);
    setDynamicImages(dynamicImagesData);
  }, []);

  useEffect(() => {
    const resizeObserver = new ResizeObserver((entries) => {
      if (entries && entries.length > 0){
         const staticImageEntry = entries[0];
         const { width, height } = staticImageEntry.contentRect;

         for (let index = 0; index < dynamicImages.length; index++) {
            updateUploadedImageDimensions(
              index,
              defaultDynamicImageDimensions.width * (width / defaultStaticImageDimensions.width),
              defaultDynamicImageDimensions.height * (height / defaultStaticImageDimensions.height)
            ); 
         }
      }
    });

    if (staticImageRef.current) {
      resizeObserver.observe(staticImageRef.current);
    }

    return () => resizeObserver.disconnect();
  }, []);

  return (
    <div className="relative">
      <Image src=... width={defaultStaticImageDimensions.width} height={defaultStaticImageDimensions.height} priority ref={staticImageRef}/>

      {dynamicImages.map((image, index) => (
        <div className="absolute" key={image.id}>
           <Image src={image.url} width={image.width} height={image.height}/>
        </div>
      ))}
    </div>
   )
CalifornianOP
Solved by starting the observer right after const dynamicImagesData = await imagesLoader(...); instead of in a separate useEffect hook, and iterating directly through dynamicImagesData instead dynamicImages (which comes asynchronously in a flow only controlled by React). Duh, what a stupid me 😕
Answer