Alert binding not triggering when parent State changes

Hi guys, I stuck with parent-child actions. I have child State and Actions inside Parent domain and I have added Scope like this:

 var body: some Reducer<State, Action> {
    Scope(state: \.paymentsState, action: /FavoritesReducer.Action.paymentsAction) {
      FavoritesPaymentReducer()
    }
    
    Reduce(self.core)
  }

and inside parent view

FavoritePaymentsView(
      store: self.store.scope(state: \.paymentsState, action: FavoritesReducer.Action.paymentsAction),

Inside payments actions (child actions) I want to ignore favorite selected and handle this action inside parent domain. It actually works when I printChanges. I mean it triggers actions and changes state to show alert, but Alert not showing up. I tried to call it with closure (I mean triggering parent with closure from child view) and it shows. How can I ignore this action inside child and handle in parent to show it properly. Because, it's a segmented view and have a lot of common alerts.

Thank you

hi @hbayramov ! Maybe you want to catch on parent reducer the child action, like:

var body: some Reducer<State, Action> {
    Scope(state: \.paymentsState, action: /FavoritesReducer.Action.paymentsAction) {
      FavoritesPaymentReducer()
    }
    
    Reduce { state, action in
        switch action {
            case .paymentsAction(.somePaymentAction):
                // catch a specific child action
                return .none

            case .paymentsAction:
                // catch-all child actions
                return .none
        }
    }
}

Hi @otondin, thank you for reply. I actually did that way. Inside core function of parent domain i have added it like this:

   case .paymentsAction(.favoriteSelected(let favorite)):
      state.selectedFavorite = favorite
      return self.handleSelectedFavorite(state: &state)
    case .paymentsAction(.favoriteActionSelected(let favorite)):
      state.selectedFavorite = favorite
      return .send(.toggleShowSelectActionsView(true))
    default:
      return .none

and it trigger and updates state. But alert not showing up in parent view.

   .floatingSheet(
      isPresented: viewStore.binding(
        get: \.showSelectActionsView,
        send: FavoritesReducer.Action.toggleShowSelectActionsView
      )
    )

But when triggering parent action like this it works

 FavoritePaymentsView(
      store: self.store.scope(state: \.paymentsState, action: FavoritesReducer.Action.paymentsAction),
      favoriteActionSelected: { favorite in
        viewStore.send(.favoriteActionSelected(favorite))
      },
      favoriteSelected: { favorite in
        viewStore.send(.favoriteSelected(favorite))
      }
    )

I just don't want to trigger via closure.

I see, but do you have the catch-all child actions case too right?

No, I just need to catch only two actions. Rest will be handled inside Child domain. These two actions common between childs. That's why I don't want to add same alerts for two different views.

@hbayramov I see, so try to implement the catch-all child actions case after your specific actions cases like this:

case .paymentsAction(.favoriteSelected(let favorite)):
      state.selectedFavorite = favorite
      return self.handleSelectedFavorite(state: &state)

case .paymentsAction(.favoriteActionSelected(let favorite)):
      state.selectedFavorite = favorite
      return .send(.toggleShowSelectActionsView(true))

case .paymentsAction:
    return .none

I have added this instead of default return none still the same, not showing up

Can you share, for a chance, your entire feature domain (parent and child) implementations?

Parent

final class FavoritesReducer: Reducer {
    
  struct State: Equatable {
    public var selectedSegment: FavoriteSegmentView.Segment
    
    public var isLoading: Bool
    public var error: Error?
    public var searchText: String
    
    public let actions: [SelectActionsType]
    
    public var showSelectActionsView: Bool
    public var showUpdateView: Bool
    public var showDeleteView: Bool
    
    public var selectedNewName: String?
    public var selectedFavorite: FavoritesUIModel.Favorite?
    
    public var paymentsState: FavoritesPaymentReducer.State
    
    public init() {
      self.selectedSegment = .payments
      self.isLoading = false
      self.searchText = .empty()
      self.actions = [.update, .changePaymentType, .delete]
      self.showSelectActionsView = false
      self.showUpdateView = false
      self.showDeleteView = false
      self.paymentsState = FavoritesPaymentReducer.State()
    }
  }
  
  enum Action: Equatable {
    case back
    
    case segmentChanged(FavoriteSegmentView.Segment)
    case searchTextChanged(String)
    
    case favoriteActionSelected(FavoritesUIModel.Favorite)
    case favoriteSelected(FavoritesUIModel.Favorite)
    
    case toggleShowSelectActionsView(Bool)
    case toggleShowUpdateView(Bool)
    case toggleShowDeleteView(Bool)
    
    case selectedAction(SelectActionsType)
                
    case dismissError
    case none
    
    case paymentsAction(FavoritesPaymentReducer.Action)
  }
  
  var body: some Reducer<State, Action> {
    Scope(state: \.paymentsState, action: /FavoritesReducer.Action.paymentsAction) {
      FavoritesPaymentReducer()
    }
    
    Reduce(self.core)
  }
  
  private func core(
    into state: inout State,
    action: Action
  ) -> Effect<Action> {
    switch action {
    case .back:
      router.dismissCoordinator()
      return .none
    case .segmentChanged(let segment):
      guard !state.isLoading else { return .none }
      state.selectedSegment = segment
      return .send(.getFavorites(segment))
    case .searchTextChanged(let text):
      return checkSearch(text: text, state: &state)
    case .favoriteActionSelected(let favorite):
      state.selectedFavorite = favorite
      return .send(.toggleShowSelectActionsView(true))
    case .favoriteSelected(let favorite):
      state.selectedFavorite = favorite
      return self.handleSelectedFavorite(state: &state)
    case .toggleShowSelectActionsView(let newValue):
      state.showSelectActionsView = newValue
      return .none
    case .toggleShowUpdateView(let newValue):
      state.showUpdateView = newValue
      return .none
    case .toggleShowDeleteView(let newValue):
      state.showDeleteView = newValue
      return .none
    case .selectedAction(let action):
      return self.handleActions(with: action, state: &state)
    case .dismissError:
      state.error = nil
      return .none
    case .none:
      return .none
    case .paymentsAction(.favoriteSelected(let favorite)):
      state.selectedFavorite = favorite
      return self.handleSelectedFavorite(state: &state)
    case .paymentsAction(.favoriteActionSelected(let favorite)):
      state.selectedFavorite = favorite
      return .send(.toggleShowSelectActionsView(true))
    case .paymentsAction:
      return .none
    }
  }
}

Child

final class FavoritesPaymentReducer: Reducer {
  
  struct State: Equatable {
    public var isLoading: Bool
    public var searchText: String
        
    public var showDebtInfoView: Bool
    public var debtCheckDate: String
    public var isDebtCheckAvailable: Bool
    public var lastRequestTime: Date
        
    public init() {
      self.isLoading = false
      self.searchText = .empty()
      self.showDebtInfoView = false
      self.debtCheckDate = .empty()
      self.isDebtCheckAvailable = false
      self.lastRequestTime = UserDefaults.lastDebtRequestTime
    }
  }
  
  enum Action: Equatable {
    case searchTextChanged(String)
    
     case favoriteActionSelected(FavoritesUIModel.Favorite)
     case favoriteSelected(FavoritesUIModel.Favorite)
    
    case toggleDebtInfoView(Bool)
    case checkIfDebtIsAvailable
    
    case none
  }
  
  var body: some Reducer<State, Action> {
    Reduce { [weak self] state, action in
      guard let self = self else { return .none }
      
      switch action {
      case .searchTextChanged(let text):
        return self.handleSearchForPayments(with: text, state: &state)
      case .toggleDebtInfoView(let value):
        state.showDebtInfoView = value
        return .none
      case .checkIfDebtIsAvailable:
        state.isDebtCheckAvailable = interactor.isDebtCheckAvailable
        return .none
      case .none:
        return .none
      default:
        return .none
      }
    }
  }
}

@hbayramov thanks! I see, I don't know if you're working with SwiftUI or UIKit, but are you aware of the BindingState feature? It might help you get reactiveness easier...

1 Like

I am working with SwiftUI. I will try it. Thanks @otondin.

@otondin I have tried it with @BindingState, it worked :slightly_smiling_face: Thank you man. Thank you for your time.

1 Like