Skip to content

Commit 1ffddd6

Browse files
committed
fix: prevent callback skipping with triggerOnce and merged refs
1 parent 59004bb commit 1ffddd6

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

src/__tests__/useInView.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,42 @@ test("should restore the browser IntersectionObserver", () => {
398398
expect(window.IntersectionObserver).toBeDefined();
399399
expect(vi.isMockFunction(window.IntersectionObserver)).toBe(false);
400400
});
401+
402+
test("should trigger all hooks when using triggerOnce with merged refs", () => {
403+
const MultipleHooksWithTriggerOnce = () => {
404+
const { ref: ref1, inView: inView1 } = useInView({ triggerOnce: true });
405+
const { ref: ref2, inView: inView2 } = useInView({ triggerOnce: true });
406+
const { ref: ref3, inView: inView3 } = useInView({ triggerOnce: true });
407+
408+
const setRefs = useCallback(
409+
(node: Element | null) => {
410+
ref1(node);
411+
ref2(node);
412+
ref3(node);
413+
},
414+
[ref1, ref2, ref3],
415+
);
416+
417+
return (
418+
<div ref={setRefs}>
419+
<div data-testid="item-1" data-inview={inView1.toString()}>
420+
{inView1.toString()}
421+
</div>
422+
<div data-testid="item-2" data-inview={inView2.toString()}>
423+
{inView2.toString()}
424+
</div>
425+
<div data-testid="item-3" data-inview={inView3.toString()}>
426+
{inView3.toString()}
427+
</div>
428+
</div>
429+
);
430+
};
431+
432+
const { getByTestId } = render(<MultipleHooksWithTriggerOnce />);
433+
434+
mockAllIsIntersecting(true);
435+
436+
expect(getByTestId("item-1").getAttribute("data-inview")).toBe("true");
437+
expect(getByTestId("item-2").getAttribute("data-inview")).toBe("true");
438+
expect(getByTestId("item-3").getAttribute("data-inview")).toBe("true");
439+
});

src/observe.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ function createObserver(options: IntersectionObserverInit) {
8282
entry.isVisible = inView;
8383
}
8484

85-
elements.get(entry.target)?.forEach((callback) => {
85+
// Copy the callbacks array before iterating to prevent issues when callbacks
86+
// are removed during iteration (e.g., when using triggerOnce)
87+
[...(elements.get(entry.target) ?? [])].forEach((callback) => {
8688
callback(inView, entry);
8789
});
8890
});

0 commit comments

Comments
 (0)