diff --git a/packages/react-intersection-observer/src/InView.tsx b/packages/react-intersection-observer/src/InView.tsx index d130060a..ab159dc5 100644 --- a/packages/react-intersection-observer/src/InView.tsx +++ b/packages/react-intersection-observer/src/InView.tsx @@ -91,6 +91,7 @@ export class InView extends React.Component< prevProps.scrollMargin !== this.props.scrollMargin || prevProps.root !== this.props.root || prevProps.threshold !== this.props.threshold || + prevProps.triggerOnce !== this.props.triggerOnce || prevProps.skip !== this.props.skip || prevProps.trackVisibility !== this.props.trackVisibility || prevProps.delay !== this.props.delay diff --git a/packages/react-intersection-observer/src/__tests__/InView.test.tsx b/packages/react-intersection-observer/src/__tests__/InView.test.tsx index 213a51cf..971e9d0c 100644 --- a/packages/react-intersection-observer/src/__tests__/InView.test.tsx +++ b/packages/react-intersection-observer/src/__tests__/InView.test.tsx @@ -144,6 +144,105 @@ test("Should unobserve when triggerOnce comes into view", () => { expect(instance.unobserve).toHaveBeenCalled(); }); +test("Should resume observing when triggerOnce is disabled while in view", () => { + const callback = vi.fn(); + const { container, rerender } = render( + + Inner + , + ); + const element = container.children[0]; + const initialObserver = intersectionMockInstance(element); + vi.spyOn(initialObserver, "unobserve"); + + mockAllIsIntersecting(true); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenNthCalledWith( + 1, + true, + expect.objectContaining({ isIntersecting: true }), + ); + expect(initialObserver.unobserve).toHaveBeenCalledWith(element); + + callback.mockClear(); + rerender( + + Inner + , + ); + const resumedObserver = intersectionMockInstance(element); + expect(resumedObserver).not.toBe(initialObserver); + + // A fresh observer reports the element's current state. + mockAllIsIntersecting(true); + expect(callback).toHaveBeenNthCalledWith( + 1, + true, + expect.objectContaining({ isIntersecting: true }), + ); + + mockAllIsIntersecting(false); + expect(callback).toHaveBeenNthCalledWith( + 2, + false, + expect.objectContaining({ isIntersecting: false }), + ); + + mockAllIsIntersecting(true); + expect(callback).toHaveBeenNthCalledWith( + 3, + true, + expect.objectContaining({ isIntersecting: true }), + ); + expect(callback).toHaveBeenCalledTimes(3); +}); + +test("Should stop observing when triggerOnce is enabled while in view", () => { + const callback = vi.fn(); + const { container, rerender } = render( + + Inner + , + ); + const element = container.children[0]; + const initialObserver = intersectionMockInstance(element); + vi.spyOn(initialObserver, "unobserve"); + + mockAllIsIntersecting(true); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenNthCalledWith( + 1, + true, + expect.objectContaining({ isIntersecting: true }), + ); + + callback.mockClear(); + rerender( + + Inner + , + ); + expect(initialObserver.unobserve).toHaveBeenCalledWith(element); + + const triggerOnceObserver = intersectionMockInstance(element); + expect(triggerOnceObserver).not.toBe(initialObserver); + vi.spyOn(triggerOnceObserver, "unobserve"); + + // A fresh observer reports the element's current state before triggerOnce stops it. + mockAllIsIntersecting(true); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenNthCalledWith( + 1, + true, + expect.objectContaining({ isIntersecting: true }), + ); + expect(triggerOnceObserver.unobserve).toHaveBeenCalledWith(element); + + mockAllIsIntersecting(false); + mockAllIsIntersecting(true); + expect(callback).toHaveBeenCalledTimes(1); +}); + test("Should unobserve when unmounted", () => { const { container, unmount } = render(Inner); const instance = intersectionMockInstance(container.children[0]);