-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Closed
Labels
bugSomething isn't working due to a bug in the library.Something isn't working due to a bug in the library.
Description
Description
If you have multiple root stores of the same feature and return a .cancel(id:) effect in one of those, it will cancel the effect in all other root stores.
Note: It is not the case when you wrap the feature inside an
_IfLetReducer.
Checklist
- I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
- If possible, I've reproduced the issue using the
mainbranch of this package. - This issue hasn't been addressed in an existing GitHub issue or discussion.
Expected behavior
It cancels only the effect of the current store
Simulator.Screen.Recording.-.iPhone.SE.3rd.generation.-.2025-04-16.at.18.10.01.mp4
Actual behavior
Cancels all effects of all stores
Simulator.Screen.Recording.-.iPhone.SE.3rd.generation.-.2025-04-16.at.18.09.30.mp4
Reproducing project
@main
struct TCAApp: App {
@State private var count = 5
var body: some Scene {
WindowGroup {
VStack {
Button("-") {
count -= 1
}
ForEach(0..<count, id: \.self) {
FeatureView(id: $0)
}
Button("+") {
count += 1
}
}
}
}
}
@ViewAction(for: Feature.self)
struct FeatureView: View {
@StateObject var store: Store<Feature.State?, Feature.Action>
@State private var isLoading = false
init(id: Int) {
self._store = .init(wrappedValue: {
.init(initialState: .init(id: id), reducer: {
Scope(state: \.self!, action: \.self) {
Feature()
}
// The following code fixes the issue
// EmptyReducer()
// .ifLet(\.self, action: \.self) {
// Feature()
// }
})
}())
}
var body: some View {
WithPerceptionTracking {
HStack {
if isLoading {
Button("Stop") {
send(.buttonTapped)
}
ProgressView()
} else {
Text("❌")
}
}
.task {
isLoading = true
await send(.task).finish()
isLoading = false
}
}
}
}
@Reducer
struct Feature {
@ObservableState
struct State: Identifiable {
let id: Int
}
enum Action: ViewAction {
case view(ViewAction)
enum ViewAction {
case task
case buttonTapped
}
}
enum CancelID {
case task
}
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .view(.task):
return .run { _ in
while !Task.isCancelled {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
}
.cancellable(id: CancelID.task)
case .view(.buttonTapped):
return .cancel(id: CancelID.task)
}
}
}
}The Composable Architecture version information
1.19.0
Destination operating system
iOS 16 - 18
Xcode version information
Xcode 16.3
Swift Compiler version information
swift-driver version: 1.120.5 Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3)
Target: arm64-apple-macosx15.0Metadata
Metadata
Assignees
Labels
bugSomething isn't working due to a bug in the library.Something isn't working due to a bug in the library.