Describe the bug
Hi! I noticed what looks like a lifecycle inconsistency in the class-based
<InView> component.
After <InView triggerOnce> receives an intersecting entry, it stops observing
as expected. If the same mounted component is then rerendered with
triggerOnce={false}, observation does not appear to resume, so later leave and
enter transitions are not reported to onChange.
From what I can tell, <InView> reinitializes its observer when options such as
root, threshold, and skip change, but componentDidUpdate() does not
currently include triggerOnce. This also seems inconsistent with the hook
lifecycle, where triggerOnce participates in recreating the observer callback.
To reproduce
The linked issue-template CodeSandbox currently uses
react-intersection-observer@9.3.3, so here is a minimal reproduction against
the current repository test suite:
test("Should resume observing when triggerOnce is disabled", () => {
const callback = vi.fn();
const { rerender } = render(
<InView triggerOnce onChange={callback}>
Inner
</InView>,
);
mockAllIsIntersecting(true);
expect(callback).toHaveBeenLastCalledWith(
true,
expect.objectContaining({ isIntersecting: true }),
);
rerender(
<InView triggerOnce={false} onChange={callback}>
Inner
</InView>,
);
callback.mockClear();
mockAllIsIntersecting(false);
expect(callback).toHaveBeenLastCalledWith(
false,
expect.objectContaining({ isIntersecting: false }),
);
});
On the current main branch, the final callback is not invoked and the
assertion fails.
Expected behavior
I would expect observation to resume after triggerOnce changes to false, so
that later leave and enter transitions can be reported.
Screenshots
Not applicable; the failing callback assertion above captures the behavior.
Desktop
- OS: macOS 26.5 (25F71)
- Browser: Not browser-specific; reproduced with the repository's Vitest
IntersectionObserver mock
- Browser version: Not applicable
Smartphone
Not tested / not applicable.
Additional context
- Confirmed in v11.0.0 and on current
main (a25b644).
- This appears to be a long-standing class-component lifecycle behavior rather
than a recent regression.
- React: 19.2.8 in the repository test suite
The relevant lifecycle path seems to be that handleChange() invokes and clears
the stored unobserve callback when inView && triggerOnce. Since
componentDidUpdate() does not compare triggerOnce, changing it from true
to false alone does not call observeNode() again.
I also checked #455/#456, #466, #512, and #758/#765. They seem to cover generic
re-observation, dynamic observer options, or hook-only work rather than this
class-component transition.
I opened #782 with a small lifecycle change and regression test. If runtime
updates to triggerOnce are intentionally unsupported for the class-based
<InView>, or if another approach would fit the project better, I’d be happy to
adjust it. Thanks for taking a look!
Describe the bug
Hi! I noticed what looks like a lifecycle inconsistency in the class-based
<InView>component.After
<InView triggerOnce>receives an intersecting entry, it stops observingas expected. If the same mounted component is then rerendered with
triggerOnce={false}, observation does not appear to resume, so later leave andenter transitions are not reported to
onChange.From what I can tell,
<InView>reinitializes its observer when options such asroot,threshold, andskipchange, butcomponentDidUpdate()does notcurrently include
triggerOnce. This also seems inconsistent with the hooklifecycle, where
triggerOnceparticipates in recreating the observer callback.To reproduce
The linked issue-template CodeSandbox currently uses
react-intersection-observer@9.3.3, so here is a minimal reproduction againstthe current repository test suite:
On the current
mainbranch, the final callback is not invoked and theassertion fails.
Expected behavior
I would expect observation to resume after
triggerOncechanges tofalse, sothat later leave and enter transitions can be reported.
Screenshots
Not applicable; the failing callback assertion above captures the behavior.
Desktop
IntersectionObservermockSmartphone
Not tested / not applicable.
Additional context
main(a25b644).than a recent regression.
The relevant lifecycle path seems to be that
handleChange()invokes and clearsthe stored
unobservecallback wheninView && triggerOnce. SincecomponentDidUpdate()does not comparetriggerOnce, changing it fromtrueto
falsealone does not callobserveNode()again.I also checked #455/#456, #466, #512, and #758/#765. They seem to cover generic
re-observation, dynamic observer options, or hook-only work rather than this
class-component transition.
I opened #782 with a small lifecycle change and regression test. If runtime
updates to
triggerOnceare intentionally unsupported for the class-based<InView>, or if another approach would fit the project better, I’d be happy toadjust it. Thanks for taking a look!