Cancel Effect when View disappears #1515
-
Hey there, I'd have a question in regards to cancelling an action when a child view disappears. I'll also attach the corresponding sample code that demonstrates the problem. Codestruct ParentState: Equatable {
var child: ChildState?
}
struct ChildState: Equatable { }
enum ParentAction {
case next
case dismissChild
case child(ChildAction)
}
enum ChildAction {
case startEffect
case onDisappear
case dismiss
}
let parentReducer = Reducer<ParentState, ParentAction, Void> { state, action, _ in
switch action {
case .next:
state.child = .init()
return .none
case .child(.dismiss), .dismissChild:
state.child = nil
return .none
case .child:
return .none
}
}
let childReducer = Reducer<ChildState, ChildAction, Void> { state, action, _ in
switch action {
case .startEffect:
return .none // Suppose we'd kick off a long running effect here
case .onDisappear:
return .cancel(id: "SomeId")
case .dismiss:
return .none
}
}
let appReducer = Reducer.combine(
childReducer
.optional()
.pullback(state: \.child, action: /ParentAction.child, environment: { () }),
parentReducer
)
.debug()
struct ParentView: View {
let store: Store<ParentState, ParentAction>
var body: some View {
WithViewStore(store) { viewStore in
Button("Open Sheet") {
viewStore.send(ParentAction.next)
}
.sheet(isPresented: viewStore.binding(get: { $0.child != nil }, send: ParentAction.dismissChild)) {
IfLetStore(store.scope(state: \.child, action: ParentAction.child)) { store in
ChildView(store: store)
}
}
}
}
}
struct ChildView: View {
let store: Store<ChildState, ChildAction>
var body: some View {
WithViewStore(store) { viewStore in
Button("Close Sheet") {
viewStore.send(ChildAction.dismiss)
}
.onDisappear {
viewStore.send(.onDisappear)
}
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 7 replies
-
Either also cancel the effect on dismiss in parent reducer or use the .task modifier on view instead of on appear/on disappear with binds the view lifecycle and the effect run. I’m pretty sure there are examples of this as this already been asked. |
Beta Was this translation helpful? Give feedback.
-
Using the view's For example, the state can be So, if you want to go that route you either need to drive navigation with a boolean, or wrap the optional child feature in an additional non-optional feature so that you can react to it. We do have a case study for this. It's a little experimental because we abstracted it as a higher-order reducer, but you might find something useful in there. And as @tgrapperon mentioned, the navigation tools we are working on will solve all of this at once. You will be able to present a screen, and when the screen goes away it will tear down all effects regardless of when they were started. |
Beta Was this translation helpful? Give feedback.
Using the view's
onDisappear
to send an action and then do cancellation inside there can be a good way to cancel effects in the child without the parent needing to do anything, but it's only workable if you are not using optional state to drive navigation. If the sheet presents/dismisses based on optional state, then there is a bit of a chicken-and-egg problem.For example, the state can be
nil
'd out to represent the sheet should go away, which then a moment later causesonDisappear
to fire, but at that point the child reducer can't receive that action because there is no state to reduce on, and hence it can't actually do any cancellation.So, if you want to go that route you either need …