Fix tabs not updating stateful child components on tab switch#6458
Fix tabs not updating stateful child components on tab switch#6458
Conversation
When switching tabs, SwiftUI reuses LoadedTabComponentView at the same structural position, preserving @State from the previous tab's children (e.g. Carousel page data, scroll index). Adding .id(selectedTabId) forces a full view recreation on each tab switch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
What might explain the Kaitlin being able to edit the and figma importer: |
MonikaMateska
left a comment
There was a problem hiding this comment.
The fix looks good!
Might be worth a follow-up at some point: if we key ComponentsView’s ForEach by a stable component id (instead of index) and have stateful bits like the Carousel re-set when their input changes, we’d fix this at the source and not rely on the parent nuking the subtree, just a thought for later.
📸 Snapshot TestBase build not foundNo build was found for the base commit 6de79d3. This is required to generate a snapshot diff for your pull request. It's possible that you created a branch off the base commit before all of the CI steps have finished processing, e.g. the one that uploads a build to our system. If that's the case, no problem! Just wait and this will eventually resolve. 🛸 Powered by Emerge Tools |
Summary
Fixes an issue where switching tabs did not update content for stateful child components (e.g. Carousel showing stale pages/scroll position from the previously selected tab).
Root Cause
LoadedTabsComponentViewrenders a singleLoadedTabComponentViewat a fixed structural position in the SwiftUI view hierarchy. When the selected tab changes, SwiftUI sees the same view type at the same position and reuses it — preserving all@Statefrom the previous tab's child components.This is compounded by
ComponentsViewusingForEach(..., id: \.offset), which keys children by array index rather than component identity. A Carousel at index 1 in Tab A is treated as the same view as a Carousel at index 1 in Tab B, so its@State(data,index,carouselHeight,autoTimer) survives the tab switch. SincesetupData()only runs in.onAppear— which doesn't re-fire for a structurally reused view — the carousel renders stale page data.Fix
Add
.id(selectedTabId)toLoadedTabComponentView. This forces SwiftUI to destroy and recreate the entire tab content subtree when the selected tab changes, ensuring all child@Stateis freshly initialized.Alternatives Considered
Fix
ComponentsViewForEach identity — Use stable component IDs (every component has anidfield from the data model) instead ofid: \.offset. This would be a more surgical fix but has broader blast radius sinceComponentsView/FlexVStack/FlexHStackare used everywhere, and changing ForEach identity can affect animation behavior and performance across all component rendering.React to tab changes inside
CarouselView— Add.onChange(of: pages)or similar to re-runsetupData()when the carousel's input data changes. This only fixes the Carousel specifically and would need to be repeated for every stateful component that could appear inside tabs.Default carousel
sizetowidth: .fill— ChangeCarouselComponentViewModelto default nil size to(width: .fill, height: .fit)instead of(width: .fit, height: .fit). This was observed to mask the issue (likely by triggering enough layout invalidation to cause re-rendering) but doesn't address the underlying view identity problem.🤖 Generated with Claude Code
Note
Low Risk
Small, localized SwiftUI identity change; main risk is unintended loss of desired per-tab state/animations or minor performance impact when switching tabs.
Overview
Fixes stale state when switching paywall V2 tabs by forcing SwiftUI to recreate the active tab subtree.
LoadedTabComponentViewis now keyed with.id(selectedTabId), ensuring stateful child components (e.g. carousels/scroll position) don’t get structurally reused across tab changes.Reviewed by Cursor Bugbot for commit 2bf2c5a. Bugbot is set up for automated code reviews on this repo. Configure here.