From 2d5d541b10a9a67b9601d5671c28f6f90e580991 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Tue, 14 Jul 2026 12:54:37 +0200 Subject: [PATCH 1/2] fix: respect threshold before trigger once cleanup --- src/__tests__/useInView.test.tsx | 32 ++++++++++++++++++++++++++++++++ src/useInView.tsx | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/__tests__/useInView.test.tsx b/src/__tests__/useInView.test.tsx index cc664e86..b3321071 100644 --- a/src/__tests__/useInView.test.tsx +++ b/src/__tests__/useInView.test.tsx @@ -118,6 +118,38 @@ test("should respect trigger once", () => { getByText("true"); }); +test("should respect the threshold before triggering once", () => { + const { getByTestId, getByText } = render( + , + ); + const wrapper = getByTestId("wrapper"); + const instance = intersectionMockInstance(wrapper); + const callback = vi.mocked(window.IntersectionObserver).mock.calls[0][0]; + const createEntry = ( + intersectionRatio: number, + ): IntersectionObserverEntry => ({ + boundingClientRect: wrapper.getBoundingClientRect(), + intersectionRatio, + intersectionRect: wrapper.getBoundingClientRect(), + isIntersecting: true, + rootBounds: null, + target: wrapper, + time: performance.now(), + }); + + React.act(() => callback([createEntry(0.25)], instance)); + + getByText("false"); + expect(instance.unobserve).not.toHaveBeenCalled(); + + React.act(() => callback([createEntry(0.5)], instance)); + + getByText("true"); + expect(instance.unobserve).toHaveBeenCalledTimes(1); +}); + test("should trigger onChange", () => { const onChange = vi.fn(); render(); diff --git a/src/useInView.tsx b/src/useInView.tsx index 791c5014..c39871e1 100644 --- a/src/useInView.tsx +++ b/src/useInView.tsx @@ -85,7 +85,7 @@ export function useInView({ }); if (callback.current) callback.current(inView, entry); - if (entry.isIntersecting && triggerOnce && unobserve) { + if (inView && triggerOnce && unobserve) { // If it should only trigger once, unobserve the element after it's inView unobserve(); unobserve = undefined; From 560e3fadfff8c403cdbe7c53327fea8cbd70acf1 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Tue, 14 Jul 2026 13:04:19 +0200 Subject: [PATCH 2/2] test: use React 17 compatible act helper --- src/__tests__/useInView.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/__tests__/useInView.test.tsx b/src/__tests__/useInView.test.tsx index b3321071..aa47c4a9 100644 --- a/src/__tests__/useInView.test.tsx +++ b/src/__tests__/useInView.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { act, render, screen } from "@testing-library/react"; import React, { useCallback } from "react"; import { defaultFallbackInView, type IntersectionOptions } from "../index"; import { @@ -139,12 +139,12 @@ test("should respect the threshold before triggering once", () => { time: performance.now(), }); - React.act(() => callback([createEntry(0.25)], instance)); + act(() => callback([createEntry(0.25)], instance)); getByText("false"); expect(instance.unobserve).not.toHaveBeenCalled(); - React.act(() => callback([createEntry(0.5)], instance)); + act(() => callback([createEntry(0.5)], instance)); getByText("true"); expect(instance.unobserve).toHaveBeenCalledTimes(1);