Skip to content

Commit 834ca14

Browse files
authored
fix: resume InView observation when triggerOnce is disabled (#782)
* fix: resume InView observation when triggerOnce is disabled * test: cover triggerOnce transitions while in view
1 parent a0877f6 commit 834ca14

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

packages/react-intersection-observer/src/InView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export class InView extends React.Component<
9191
prevProps.scrollMargin !== this.props.scrollMargin ||
9292
prevProps.root !== this.props.root ||
9393
prevProps.threshold !== this.props.threshold ||
94+
prevProps.triggerOnce !== this.props.triggerOnce ||
9495
prevProps.skip !== this.props.skip ||
9596
prevProps.trackVisibility !== this.props.trackVisibility ||
9697
prevProps.delay !== this.props.delay

packages/react-intersection-observer/src/__tests__/InView.test.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,105 @@ test("Should unobserve when triggerOnce comes into view", () => {
144144
expect(instance.unobserve).toHaveBeenCalled();
145145
});
146146

147+
test("Should resume observing when triggerOnce is disabled while in view", () => {
148+
const callback = vi.fn();
149+
const { container, rerender } = render(
150+
<InView triggerOnce onChange={callback}>
151+
Inner
152+
</InView>,
153+
);
154+
const element = container.children[0];
155+
const initialObserver = intersectionMockInstance(element);
156+
vi.spyOn(initialObserver, "unobserve");
157+
158+
mockAllIsIntersecting(true);
159+
expect(callback).toHaveBeenCalledTimes(1);
160+
expect(callback).toHaveBeenNthCalledWith(
161+
1,
162+
true,
163+
expect.objectContaining({ isIntersecting: true }),
164+
);
165+
expect(initialObserver.unobserve).toHaveBeenCalledWith(element);
166+
167+
callback.mockClear();
168+
rerender(
169+
<InView triggerOnce={false} onChange={callback}>
170+
Inner
171+
</InView>,
172+
);
173+
const resumedObserver = intersectionMockInstance(element);
174+
expect(resumedObserver).not.toBe(initialObserver);
175+
176+
// A fresh observer reports the element's current state.
177+
mockAllIsIntersecting(true);
178+
expect(callback).toHaveBeenNthCalledWith(
179+
1,
180+
true,
181+
expect.objectContaining({ isIntersecting: true }),
182+
);
183+
184+
mockAllIsIntersecting(false);
185+
expect(callback).toHaveBeenNthCalledWith(
186+
2,
187+
false,
188+
expect.objectContaining({ isIntersecting: false }),
189+
);
190+
191+
mockAllIsIntersecting(true);
192+
expect(callback).toHaveBeenNthCalledWith(
193+
3,
194+
true,
195+
expect.objectContaining({ isIntersecting: true }),
196+
);
197+
expect(callback).toHaveBeenCalledTimes(3);
198+
});
199+
200+
test("Should stop observing when triggerOnce is enabled while in view", () => {
201+
const callback = vi.fn();
202+
const { container, rerender } = render(
203+
<InView triggerOnce={false} onChange={callback}>
204+
Inner
205+
</InView>,
206+
);
207+
const element = container.children[0];
208+
const initialObserver = intersectionMockInstance(element);
209+
vi.spyOn(initialObserver, "unobserve");
210+
211+
mockAllIsIntersecting(true);
212+
expect(callback).toHaveBeenCalledTimes(1);
213+
expect(callback).toHaveBeenNthCalledWith(
214+
1,
215+
true,
216+
expect.objectContaining({ isIntersecting: true }),
217+
);
218+
219+
callback.mockClear();
220+
rerender(
221+
<InView triggerOnce onChange={callback}>
222+
Inner
223+
</InView>,
224+
);
225+
expect(initialObserver.unobserve).toHaveBeenCalledWith(element);
226+
227+
const triggerOnceObserver = intersectionMockInstance(element);
228+
expect(triggerOnceObserver).not.toBe(initialObserver);
229+
vi.spyOn(triggerOnceObserver, "unobserve");
230+
231+
// A fresh observer reports the element's current state before triggerOnce stops it.
232+
mockAllIsIntersecting(true);
233+
expect(callback).toHaveBeenCalledTimes(1);
234+
expect(callback).toHaveBeenNthCalledWith(
235+
1,
236+
true,
237+
expect.objectContaining({ isIntersecting: true }),
238+
);
239+
expect(triggerOnceObserver.unobserve).toHaveBeenCalledWith(element);
240+
241+
mockAllIsIntersecting(false);
242+
mockAllIsIntersecting(true);
243+
expect(callback).toHaveBeenCalledTimes(1);
244+
});
245+
147246
test("Should unobserve when unmounted", () => {
148247
const { container, unmount } = render(<InView triggerOnce>Inner</InView>);
149248
const instance = intersectionMockInstance(container.children[0]);

0 commit comments

Comments
 (0)