reactjsnext.jsgetstaticprops

Intersection observer only works with the last element in Next.js


Intersection observer only works with the last element in Next.js

I want to target all the repeated div elements and put an opacity effect(0 or 1) every time I see them on the viewport, but only the last div element ends up having the opacity effect.

This is my code:

const AboutMe: NextPage = ({ data }: any) => {
  const target = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let observer: IntersectionObserver;
    if (!target.current) return;
      if (target) {
        observer = new IntersectionObserver(
          ([e]) => {
            const target = e.target as HTMLElement;
            if (e.isIntersecting) {
              target.style.opacity = "1";
            } else {
              target.style.opacity = "0";
            }
          },
          { threshold: 0.5 }
        );
        observer.observe(target.current as Element);
        }
    }, [target]);

    return (
      <main>
        <section className="container px-5 py-24 mx-auto space-y-20">
          {data.results?.map((item: any) => {
              return (
                  <div
                      className="opacity-0 transition-all duration-500"
                      key={item.id}
                      ref={target}
                  >
                    <ol>
                      <li>
                        {
                          item.properties.content.rich_text[0]
                              .plain_text
                        }
                      </li>
                    </ol>
                  </div>
                );
            })}
        </section>
      </main>
    );
};

export default AboutMe;


export async function getStaticProps() {
    const options = {
    ...
    };

    const res = await fetch(
        `https://api...`,
        options
    );
    const data = await res.json();

    return {
        props: { data },
    };
}

How can I solve this?


Solution

  • There are several issues with your code.

    1. You are re-using the same ref object for multiple HTML elements. This is not going to work this way. You need to store each element's ref in its own ref object.

    2. React effects do not run when ref object changes. Your current effect just happens to run once the component mounts.

    To keep the implementation clean, you can refactor your appearing/disappearing divs into a separate component with its own IntersectionObserver.

    import { NextPage } from "next";
    import { useEffect, useRef, useState } from "react";
    
    function Item({ item }: { item: any }) {
      const ref = useRef<HTMLDivElement | null>(null);
      const [visible, setVisible] = useState(false);
    
      useEffect(() => {
        const observer = new IntersectionObserver(
          (entries) => {
            entries.forEach(({ target, isIntersecting }) => {
              if (target === ref.current) {
                setVisible(isIntersecting);
              }
            });
          },
          {
            threshold: 0.5,
          }
        );
    
        if (ref.current) {
          observer.observe(ref.current);
        }
    
        return () => {
          observer.disconnect();
        };
      }, []);
    
      return (
        <div
          ref={ref}
          className={`transition-all duration-500 ${
            visible ? "opacity-100" : "opacity-0"
          }`}
        >
          <ol>
            <li>{item.properties.content.rich_text[0].plain_text}</li>
          </ol>
        </div>
      );
    }
    
    const AboutMe: NextPage = ({ data }: any) => {
      return (
        <main>
          <section className="container px-5 py-24 mx-auto space-y-20">
            {data.results?.map((item: any) => (
              <Item key={item.id} item={item} />
            ))}
          </section>
        </main>
      );
    };
    
    export default AboutMe;
    

    p.s. Please also consider replacing all any hacks with specific type definitions, as using any defeats the purpose of using TypeScript in a project.