Fix(Swiper): ensure initial slide is displayed - #3332
Conversation
📝 WalkthroughWalkthroughSwiper initial navigation now waits for committed layout across two animation frames, clamps the initial page, and avoids redundant synchronization. Scroll handling and lifecycle cleanup were tightened, tests cover the behavior, and modal examples now use ChangesSwiper initialization and modal updates
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Swiper
participant Browser
participant User
Swiper->>Browser: Defer initial page scroll
Browser->>Swiper: Complete guarded initialization
User->>Swiper: Trigger scroll event
Swiper->>Swiper: Refresh navigation state
Swiper->>Browser: Schedule debounced scroll handling
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
8710df1 to
67a2f92
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
lib/src/components/Swiper/index.test.tsx (1)
178-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename the
use*helpers — they aren't hooks.
useSyncAnimationFrames/useCapturedAnimationFramesare plain setup helpers called insideit()callbacks; theuseprefix misleads readers and can tripreact-hooks/rules-of-hooksif that rule is enabled for test files.mockSyncAnimationFrames/captureAnimationFramesread better.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/components/Swiper/index.test.tsx` around lines 178 - 215, Rename the plain test setup helpers useSyncAnimationFrames and useCapturedAnimationFrames to mockSyncAnimationFrames and captureAnimationFrames, respectively, and update every invocation in the related tests. Preserve their existing animation-frame mocking behavior.lib/src/components/Swiper/index.tsx (1)
237-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a stable debounced handler instead of a mutable ref.
Two smells here:
handleScrollRef.current = handleScrollmutates a ref during render (not concurrent-render safe), and the previous debounced instance is never cancelled whenhandleScrollis re-memoized on everycurrentPagechange — a pending call from the old instance can still fire with a staleupdatePageclosure. A singledebouncecreated once, calling through refs to the latestgetNavigationState/updatePage, avoids both.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/components/Swiper/index.tsx` around lines 237 - 240, Replace the mutable handleScrollRef pattern with one stable debounced handler created once, while routing its invocation through refs that are updated to the latest getNavigationState and updatePage callbacks. Ensure the previous debounced instance is cancelled when dependencies are re-memoized, preventing stale pending calls while preserving current-page behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/src/components/Swiper/index.test.tsx`:
- Around line 295-302: Restore the original HTMLElement.prototype offsetWidth
and scrollWidth descriptors after the test that patches them, using test-scoped
setup/cleanup or a finally-style teardown so later tests observe the pre-test
geometry. Keep the mocked 896 and 2688 values available for the intended test
while ensuring cleanup runs even when assertions fail.
In `@lib/src/components/Swiper/index.tsx`:
- Around line 336-353: In the deferred initialization callback around goTo and
setCurrentPage, arm skipSnapBackRef only when goTo will produce an actual
scroll, and arm skipNextPageScrollRef only when setCurrentPage(initialPage) will
change the current page. Avoid setting either one unconditionally, preserving
initialization behavior while preventing unused one-shot guards from affecting
later user or external navigation.
---
Nitpick comments:
In `@lib/src/components/Swiper/index.test.tsx`:
- Around line 178-215: Rename the plain test setup helpers
useSyncAnimationFrames and useCapturedAnimationFrames to mockSyncAnimationFrames
and captureAnimationFrames, respectively, and update every invocation in the
related tests. Preserve their existing animation-frame mocking behavior.
In `@lib/src/components/Swiper/index.tsx`:
- Around line 237-240: Replace the mutable handleScrollRef pattern with one
stable debounced handler created once, while routing its invocation through refs
that are updated to the latest getNavigationState and updatePage callbacks.
Ensure the previous debounced instance is cancelled when dependencies are
re-memoized, preventing stale pending calls while preserving current-page
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e1cffa4e-c1ea-4a56-8dbb-9aa7b9aa9ce7
📒 Files selected for processing (5)
lib/src/components/Modal/docs/examples/asset-swiper-title.tsxlib/src/components/Modal/docs/examples/asset-swiper.tsxlib/src/components/Swiper/index.test.tsxlib/src/components/Swiper/index.tsxlib/src/components/Swiper/utils.ts
42cdc70 to
b214b75
Compare
guillaumewttj
left a comment
There was a problem hiding this comment.
I'm ok with the behavior / functionnal part of the code ... but i think that we're at a poing where the amount of comments we have in there is a strong signal to break this component into functions / hooks.
Maybe using the principle of VM + breaking each action / reaction into a named function would help reading this component more easily ?
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
b214b75 to
05503b6
Compare
@guillaumewttj PR ready for review: #3342 |
Bug
Swiper's
initialIndexoption was ignored whenever the swiper mounted into a subtree that was still painting (happened to me in a Next.js app) because of internal mechanism, it would always show the first slide.Timeline:
scroll-snap-type: x mandatory. A scroll issued mid-first-layout isn't a settled snap point yet, so the browser's snap correction yanked it back to offset 0px.currentPageto 0. A one-time init flag then blocked any retry, so the wrong position was permanent.A Vite SPA escaped it only by luck: its client-only mount left layout already settled before the react effect ran, so the initial scroll landed on a valid snap point and stuck.
Two latent bugs sat in the same path:
initialIndexof 0 computed to slide -1 (it's a 1-based value)initialIndexproduced NaN, both were used into the scroll mathFix (thank you Claude)
Defer the initial scroll to after the first paint:
Ignore the swiper's own scroll once:
Sync state directly at init:
none of the unit tests reproduce the bug
jsdom has no layout, paint, or scroll-snap. Tests are here to lock the fix's mechanism against future refactors (that the scroll is deferred, and that the guard is one-shot rather than permanent).
Bonus fixes
Two doc examples (asset-swiper, asset-swiper-title) were also fixed: their assets weren't wrapped in Swiper.Slides, so the swiper never registered any slides and initialIndex did nothing.
Screen captures from marketplace-front X Next.js
Before
brand-profile-swiper-bug.mp4
After
brand-profile-swiper-bug-fix.mp4
Summary by CodeRabbit
Bug Fixes
Examples
Tests