Vishal
Vishal

Reputation: 6378

Intersection Observer is not trustable?

I am changing the background color of a table cell when it is fully visible. To accomplish this task I have used an intersection observer.

All the code is available on code sandbox with demo reproducing the bug:

Edit dawn-sunset-9ki0d

I am using useInView hook for the intersection observer:

export const useInView = options => {
  const ref = useRef();
  const [isVisible, setIsVisible] = useState(false);
  const [intersectionRatio, setIntersectionRatio] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      console.log("called");
      setIsVisible(entry.isIntersecting);
      setIntersectionRatio(entry.intersectionRatio);
    }, options);

    if (ref.current) observer.observe(ref.current);

    return () => {
      if (ref.current) observer.unobserve(ref.current);
    };
  }, [ref, options]);

  return [ref, isVisible, intersectionRatio];
};

Here is the table in which I am rendering the data:



 <div id="my-table" style={{ height: 200, width: 200, overflow: "auto" }}>
    <table>
      <tbody>
        {tableValues.map((row, rowIndex) => (
          <tr key={rowIndex}>
            {row.map((cell, cellIndex) => (
              <CellRendererContainer
                key={`${rowIndex}${cellIndex}`}
                rowIndex={rowIndex}
                cellIndex={cellIndex}
                tableCell={cell}
              />
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  </div>

The intersection observer is used in CellRenderer, which is divided into two files:

CellRendererContainer.js

const CellRendererContainer = ({ rowIndex, cellIndex, tableCell }) => {
  const [ref, isVisible, intersectionRatio] = useInView({
    root: document.querySelector("#my-table"),
    rootMargin: "0px",
    threshold: 0.0
  });

  return (
    <CellRenderer
      ref={ref}
      isVisible={isVisible}
      intersectionRatio={intersectionRatio}
      rowIndex={rowIndex}
      cellIndex={cellIndex}
      tableCell={tableCell}
    />
  );
};

And here is the actual rendering of the cell, CellRenderer.js

const CellRenderer = React.forwardRef(
  ({ isVisible, intersectionRatio, rowIndex, cellIndex, tableCell }, ref) => (
    <td
      ref={ref}
      style={{
        padding: 25,
        backgroundColor:
          rowIndex > 0 && cellIndex > 0 && isVisible && intersectionRatio > 0.9
            ? "red"
            : "white"
      }}
    >
      {tableCell}
    </td>
  )
);

The problem in the current implementation is that: intersection observer's callback for some items is not being called.

Expected result:

Any cell that becomes visible should have a red background as soon as it is visible fully. Otherwise, that cell should be white.

Code sandbox link: (so that you does not need to scroll to the top)

Edit dawn-sunset-9ki0d

Upvotes: 5

Views: 4962

Answers (1)

Ouroborus
Ouroborus

Reputation: 16885

The IntersectionObserver constructor signature is:

var observer = new IntersectionObserver(callback[, options]);

The options argument is optional and, if supplied, should be an object with properties that describe how you'd like the newly created IntersectionObserver to behave.

In hook.js, you have this line:

const observer = new IntersectionObserver(([entry]) => {
  console.log("called");
  setIsVisible(entry.isIntersecting);
  setIntersectionRatio(entry.intersectionRatio);
}, options);

Your variable options isn't set to something that would be useful in this context.

Instead, something like this does what you're looking for:

const observer = new IntersectionObserver(([entry]) => {
  console.log("called");
  setIsVisible(entry.isIntersecting);
  setIntersectionRatio(entry.intersectionRatio);
}, {
  threshold: 0.9
});

After this change, the event would be triggered when the relevant elements become more or less than 90% visible.

Upvotes: 6

Related Questions