swift-composable-architecture

How to implement a working confirmationDialog in The (Swift) Composable Architecture 1.0


In pre 1.0 TCA, there was relatively straightforward was to present a ConfirmationDialog

When the optional was populated with state (e.g. made non-nil), the dialog would appear

.confirmationDialog( store.scope(state: \.deleteConfirmationDialog), dismiss: .authAction(.dismissLogoutConfirmationDialog))

But this code does not compile in 1.0. What's the new syntax?


Solution

  • For TCA 1.2.0 you can use this code for your View:

    struct MyView: View {
        var body: some View {
           WithViewStore(self.store, observe: { $0 }) { viewStore in
               HStack {
                  Button("Show Confirmation Dialog") {
                      viewStore.send(.showConfirmationDialog)
                  }
               }
               .confirmationDialog(
                        store: self.store.scope(
                            state: \.$destination,
                            action: { .destination($0) }),
                        state: /YourDomainFeature.Destination.State.confirmationDialog,
                        action: YourDomainFeature.Destination.Action.confirmationDialog
                    )
           }
        }
    }
    

    And in your reducer file YourDomainFeature.swift

    struct YourDomainFeature: Reducer { 
        struct State: Equatable {
            @PresentationState var destination: Destination.State?
        }
    
        enum Action: Equatable {
            case destination(PresentationAction<Destination.Action>)
            case showConfirmationDialog
            enum ConfirmationDialog: Equatable {
                case cancelTapped
                case yourButtonOptionTapped
            }
        }
    
        var body: some ReducerOf<Self> {
            Reduce { state, action in
                switch action { 
                case .showConfirmationDialog:
                    state.destination = .confirmationDialog(.showConfirmationDialog())
                    return .none
                case .destination(.presented(.confirmationDialog(.cancelTapped))):
                // Do something when cancel button is tapped
                    return .none
                case .destination(.presented(.confirmationDialog(.yourButtonOptionTapped))):
                // Do something when your button option is tapped
                    return .none
                case .destination(.dismiss):
                    return .none
                }
            }
            .ifLet(\.$destination, action: /Action.destination) {
                Destination()
            }
        }
    }
    extension YourDomainFeature {
        struct Destination: Reducer {
        
            enum State: Equatable {
                case confirmationDialog(ConfirmationDialogState<YourDomainFeature.Action.ConfirmationDialog>)
            }
        
            enum Action: Equatable {
                case confirmationDialog(YourDomainFeature.Action.ConfirmationDialog)
            }
        
            var body: some ReducerOf<Self> {
                Reduce { state, action in
                    return .none
                }
           }
        }
    }
    
    extension ConfirmationDialogState where Action == YourDomainFeature.Action.ConfirmationDialog {
        static func showConfirmationDialog() -> Self {
            ConfirmationDialogState(title: TextState("Dialog Title"), buttons: [
                .default(TextState("Button Option Title"), action: .send(.yourButtonOptionTapped)),
                .cancel(TextState("Cancel"))
            ])
        }
    }