5

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

Yushin
  • 1,684
  • 3
  • 20
  • 36
Vishal
  • 6,238
  • 10
  • 82
  • 158
  • I poked around enough to see that the events **are** getting fired correctly but your code doesn't seem to always update the color correctly in response to the event. (Judicious use of `console.log` can help with debugging.) – Ouroborus Nov 26 '19 at 17:02
  • @Ouroborus Thanks for verifying that. I will debug the rendering and color corrections. – Vishal Nov 26 '19 at 20:03

1 Answers1

6

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.

Ouroborus
  • 16,237
  • 4
  • 39
  • 62
  • If I check that with a higher threshold like `1.0` or `0.9` then looks like it is working fine. But if I send `0.0` to threshhold, then it does not work. Can you explain that why? – Vishal Nov 30 '19 at 09:08
  • @Vishal intersectionRatio is used to determine the transition between hidden and visible. If you set it to 0, you'll never get a transition because the element never becomes hidden (never has an intersection ratio less than 0). – Ouroborus Nov 30 '19 at 18:51
  • A big thank you for clearing my doubt. I have 2 other doubts, if you can explain them, then it will be more than helping me out of this scenario. Q1: I know that threshhold can also accept an array of numbers between 0.0 and 1.0, What is the use of that array. Is this array has anything related to my scenario? Q2: If I do not pass root and rootMargin, then will it take its default values? If so, then If I pass in root only, then will it take threshhold = 1.0 by default? – Vishal Nov 30 '19 at 19:00
  • The codesandbox is just an example. I need to check that in my real scenario. I hope that this thing will work. My real project code is in my office PC. So, I will check that on monday and will then come back to you. Since, my question is solved right now, i will mark this answer as accepted. – Vishal Nov 30 '19 at 19:00
  • As per the rules of stackoverflow, I will only be allowed to award the bounty after 24 hours. – Vishal Nov 30 '19 at 19:01
  • There is an [example of using an array of thresholds](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Thresholds). As you might expect, you'll get an event for each transition set in the threshold list. For example, `[0.4, 0.6]` will give you events when either 40% or 60% visibility is crossed. In this way, you can get multiple events as an element's visibility increases or decreases. – Ouroborus Dec 01 '19 at 00:57
  • If you don't specify a particular property in the options object, it will take on its default value. – Ouroborus Dec 01 '19 at 00:58