Doubt about creating a custom alert view with ViewModifier and its own Reducer

Hi, I'm trying to create a reusable custom alert with its own reducer. This will not use the .alert view modifier but a ViewModifier instead, in order to customize all the UI.

Right now I'm working on the Reducer and found a brick wall that makes me think again about my approach. This is my Reducer code.

struct CustomAlertReducer<ButtonAction: Equatable>: Reducer {
    
    @Dependency(\.dismiss) var dismiss
    
    struct State: Equatable {
        public var title: String?
        public var message: String?
        public var image: URL?
        public var buttons: [CustomAlertButtonState<ButtonAction>]
        public var bottomButton: CustomAlertButtonState<ButtonAction>?
    }
    
    enum Action: Equatable {
        case cancel
        case action(ButtonAction)
    }
    
    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case .cancel:
                return .run { _ in
                    await self.dismiss()
                }
            case .action:
                // The actions should be handled by the reducer displaying the alert
                return .none
            }
        }
    }
}

struct CustomAlertButtonState<Action>: Identifiable, Equatable {

    public let id: UUID
    public let text: String
    public let action: Action
    
    init(
        text: String,
        action: Action
    ) {
        self.id = UUID()
        self.text = text
        self.action = action
    }
    
    public static func == (lhs: CustomAlertButtonState, rhs: CustomAlertButtonState) -> Bool {
        lhs.text == rhs.text
    }
}

Going this way is obviously forcing me to add the in the view for CustomAlertReducer in order to be able to use it, forcing me to this weird code


struct CustomAlert<ButtonAction: Equatable>: View {

let viewStore: ViewStore<CustomAlertReducer<ButtonAction>.State?, CustomAlertReducer<ButtonAction>.Action>

...
}

The use of an optional state on the ViewStore is due to the ability to deal with state being nil and then hiding the view modifier.

Should be this the proper way to handle this situation? Not sure if this is the best approach to deal with ViewModifiers on TCA (with present and dismiss animations), but is the best I could find until now.

My final goal is to deal with the ViewModifiers visibility using an Alert reducer similar to the Destination one described on the documentation

struct MyReducer: Reducer {

    struct State: Equatable {
        @PresentationState var alert: Alert.State?

    //...
    }

    struct Alert: Reducer {
        enum State: Equatable {
            case whisper(WhisperReducer.State) // This is another kind of custom alert
            case customAlert(CustomAlertReducer.State)
        }
        
        enum Action: Equatable {
            case whisper(WhisperReducer.Action)
            case customAlert(CustomAlertReducer.Action)
        }
        
        var body: some ReducerOf<Self> {
            Scope(state: /State.whisper, action: /Action.whisper) {
                WhisperReducer()
            }
            
            Scope(state: /State.customAlert, action: /Action.customAlert) {
                CustomAlertReducer()
            }
        }
    }

    var body: some Reducer<State, Action> {
        Reduce { state, action in
            //...
        }
        .ifLet(\.$alert, action: /Action.alert) {
            Alert()
        }
    }
}

Thanks for your time.