IfLetStore and Effect cancellation on view disappear

I tried your mentioned approach here now:

The code is identical to your approach expect for one difference: it doesn't nil out the state in AppAction.detail.onDisappear because doing so would not allow for AppAction.detail.avatar1.onDisappear and AppAction.detail.avatar2.onDisappear to run. In other words this causes crash:

        case .detail(.onDisappear):
            state.detail = nil
            return .none

and this won't crash

        case .detail(.onDisappear):
            return .none

I am not 100% confident about this and I fear leaving the state "dangling" OR relying on the state to be "dangling" may cause new issues. Not sure. Would love to hear your thoughts on this.

I think this could definitely be improved. I experimented with an additional state wrapper (with a dedicated reducer) that holds the optional state and performs some actions when the wrapped state is set to nil. Found out it's not trivial to implement, as many corner cases should be rethought and handled gracefully. Something like this already exists in the Swift Composable Architecture repository - Lifecycle reducer example. It could be a good starting point.

The solution I came to use is somewhat a hybrid of what you've been discussing and an approach that @mbrandonw used in a TCA sample code.

Every composable feature alongside the reducer defines something like:

enum PermissionsTeardownToken: CaseIterable, Hashable {
  case locationManagerId
}

Which simply defines tokens for all long-running Effects for that reducer. So when parent to Permissions reducer decides to nil it's state out:

    case .stop:
      state.permissions = nil
      return .cancel(token: PermissionsTeardownToken.self)

Where cancel(token:) is a tiny extension on Effect:

extension Effect {
  static func cancel<T>(token: T.Type) -> Effect where T: CaseIterable, T: Hashable {
    .merge(token.allCases.map(Effect.cancel(id:)))
  }
}
3 Likes

Interesting. I like the idea.

Doesn't this solution assume there's just one "Permissions"-view? What if you had multiple instances of the Permissions-view at the same time? I think it is important that there's a cancellation id / token passed down.

1 Like

Doesn't this solution assume there's just one "Permissions"-view? What if you had multiple instances of the Permissions-view at the same time? I think it is important that there's a cancellation id / token passed down.

Right! This is very important. Thanks for bringing it up here. For that reason, in my example project, I stored the effect id in the state struct. I don't think it's a perfect solution, but I couldn't find a better one.

Yep

For that reason, in my example project, I stored the effect id in the state struct

I fear it is a bit scary to store it in the state as the state tree is optional and leaving the state "dangling around" for a little while is a solution that might cause new issues.

New thread started here How can The Composable Architecture really become composable? - #2 by hfossli. I thought it would be a good idea to summarise and tell the story of the problem step by step.

1 Like

There's an update over here How can The Composable Architecture really become composable?