You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Store.ifLet(then:else:) was deprecated in TCA 1.24.0 in favor of Observation-based tooling. The two APIs have fundamentally different semantics that make a direct migration non-trivial, even when both parent and child states adopt @ObservableState.
ifLet(then:else:) fires only when the optional transitions between nil and non-nil. Internal mutations to the child state do not re-fire the then closure.
The observe { if let store = store.scope(...) } replacement fires whenever the optional's value changes, including internal child state mutations.
Checklist
I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
If possible, I've reproduced the issue using the main branch of this package.
After the nil → non-nil transition, subsequent child state mutations should not re-enter the observe block. The behavior should match ifLet(then:else:).
Actual behavior
The observe block re-fires on every child state mutation while the optional remains non-nil. In a UIKit context, this means view creation code executes multiple times, resulting in duplicate views or view controllers being added to the hierarchy.
Reproducing project
// First tap: nil → non-nil
observe: called ✅
// Second tap: child state mutation (title updated, feedback still non-nil)
observe: called ❌ → duplicate view added to stack
Description
Store.ifLet(then:else:)was deprecated in TCA 1.24.0 in favor of Observation-based tooling. The two APIs have fundamentally different semantics that make a direct migration non-trivial, even when both parent and child states adopt@ObservableState.ifLet(then:else:)fires only when the optional transitions between nil and non-nil. Internal mutations to the child state do not re-fire thethenclosure.The
observe { if let store = store.scope(...) }replacement fires whenever the optional's value changes, including internal child state mutations.Checklist
mainbranch of this package.Expected behavior
After the nil → non-nil transition, subsequent child state mutations should not re-enter the
observeblock. The behavior should matchifLet(then:else:).Actual behavior
The
observeblock re-fires on every child state mutation while the optional remains non-nil. In a UIKit context, this means view creation code executes multiple times, resulting in duplicate views or view controllers being added to the hierarchy.Reproducing project
// First tap: nil → non-nil
observe: called ✅
// Second tap: child state mutation (title updated, feedback still non-nil)
observe: called ❌ → duplicate view added to stack
Preview
if-let-store.mp4.mp4
Xcode Project
→ https://github.com/renatorodrigues/if-let-store
The Composable Architecture version information
1.24.1
Destination operating system
iOS 26.2 / iOS 18.2
Xcode version information
26.2.0
Swift Compiler version information