If an action is triggered during other action’s mutate, the action is handled in other loop.

case .tapDeleteButton(let model):
          let deleteHandler: () -> Void = { [weak self, weak listAdapter] in
            self?.action.onNext(.deletePositionCell(model?.userCash))

            listAdapter?.performUpdates(animated: true, completion: { [weak self] _ in // line 1
              self?.action.onNext(.delete(model?.userCash))
            })
          }
          self?.action.onNext(.showDeleteAlert(deleteHandler))
        }
case .showDeleteAlert(let deleteAction):
            guard let deleteAction = deleteAction else { return .empty() }
            deleteAction()
            return .empty()
case .deletePositionCell(let userCash):
            return .just(.deletePositionCell(userCash)) // line 2

line 1 is called before line 2.

Showing loading view while calling API and process the response ReactorKit

.concat(
                    .just(.setIsLoading(true)),
                    getAPIData(),
                    .just(.setIsLoading(false))

    private func getAPIData( ) -> Observable<Mutation> {
        return repository
            .getAPI()
            .asObservable()
            .catchErrorAndShowToastAndJustComplete()
            .map { .setDTO($0, mode) }
    }

setIsLoading(false) is executed after API response is gotten.

concat wait for the element to complete

Why use distinctUntilChanged Reactor state changes every time when one of the variables change

reactor.state.map(\.isRefreshing)
            .distinctUntilChanged()
            .filter { !$0 }
            .observe(on: MainScheduler.instance)
            .subscribe(onNext: { [weak self] _ in
                self?.collectionView.refreshControl?.endRefreshing(delay: .default)
            })
            .disposed(by: disposeBag)

So if you remove distinctUntilChanged, the subscribe block executes when other variables change, when there’s no need to be executed.

When you need to diverge mutation in mutate ReactorKit

func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .updatePortfolioView(let portfolioView):
            let newCurrency = portfolioView.currency
            let refreshIfCurrencyChanged: Observable<Mutation> = {
                if newCurrency != currentState.currency {
                    return getProfit(timeRange: currentState.timeRange, currency: newCurrency, assetFilter: currentState.assetFilter)
                } else {
                    return .empty()
                }
            }()

            return .merge(
                refreshIfCurrencyChanged,
                .just(
                    .batch([
                        .setPortfolioView(portfolioView),
                        .setCurrency(portfolioView.currency),
                        .updateUnrealizedProfitStockModels,
                        .updateRealizedProfitStockModels,
                        .updateDividendStockModels,
                        .updateTotalSumModel,
                    ])
                )
            )

calling API with changed state

     func mutate(action: Action) -> Observable<Mutation> {
         switch action {
         case .initialize:
             return .concat(              .just(.setCurrency(userDefaultSettings.portfolioTabDisplayCurrencyPreference)),
                getProfit()
             )
     }
}

private func getProfit(timeRange: ProfitTimeRange, targetCurrency: Currency, assetFilter: AssetFilter) -> Observable<Mutation> {
        return profitRepository.getProfit(timeRange: currentState.timeRange, targetCurrency: currentState.currency, assetFilter: currentState.assetFilter)
            .asObservable()
            .catchErrorAndShowToastAndJustComplete()
            .map {
                .batch([
                    .setProfitInfoDTO($0),
                    .updateUnrealizedProfitStockModels,
                    .updateRealizedProfitStockModels,
                    .updateDividendStockModels,
                    .updateTotalSumModel,
                ])
            }
    }

If you have this concat return of mutation like this, calling API in getProfit function does not have changed currency. Current state changed after the all concat mutation is executed.

Therefore you need to put parameter.

func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .initialize:
            let currency = userDefaultSettings.portfolioTabDisplayCurrencyPreference
            return .concat(
                .just(
                    .setCurrency(currency)),
                      getProfit(timeRange: currentState.timeRange, currency: currency, assetFilter: currentState.assetFilter)
            )
}

Reactor writing tip

  1. Use Inject for repositories
    @Inject private var bankAccountRepository: BankAccountRepositoryType
  1. Use InitParams for initializer to set initial state
struct InitParams {
     var portfolioView: PortfolioView
}

var initialState: State

init(params: InitParams) {
        self.initialState = State(portfolioView: params.portfolioView)
    }
  1. To use dependency container to initiate the view controller as a parameter of the other view controller
final class PortfolioDividendViewController: UIViewController, View {
    typealias Reactor = PortfolioDividendViewReactor
    typealias Factory = (Reactor.InitParams) -> PortfolioDividendViewController
}

final class ExecutionContext {
    init() {
         container.register { (params: PortfolioDividendViewReactor.InitParams) -> PortfolioDividendViewController in
                let reactor = PortfolioDividendViewReactor(params: params)
                return PortfolioDividendViewController(
                    reactor: reactor
                )
            }

         container.register { [unowned container] (params: AnalysisViewReactor.InitParams) -> AnalysisViewController in
                let reactor = AnalysisViewReactor(
                    initParams: params
                )
                return AnalysisViewController(
                    reactor: reactor,
                    dividendViewControllerFactory: try container.resolve()
                )
            }

         container.register { [unowned container] () -> PortfolioDividendViewController.Factory in
                { [container] in try! container.resolve(arguments: $0) }
            }
    }
}

How to add action to cell using IGListKit, ReactorKit

  1. Cell
final class ExampleCell: UICollectionViewCell, Reusable {

    enum Event {
        case tapButton(Bool)
    }

    private func bind() {
        button?.rx.tapGesture().when(.recognized)
            .asPublisherIgnoringError()
            .sink(receiveValue: { [weak self] _ in
                guard let isSomething = self?.isSomething else { return }
                self?.isSomething.toggle()
                self?.onEvent?(.tapButton(isSomething))
            })
            .store(in: &subscriptions)
    }
}

2. SectionController

final class ExampleSectionController: NeoListSectionController {
    typealias Model = ExampleModel
    typealias Cell = ExampleCell

    private var model: Model?
    private let eventSubject = PublishSubject<ExampleCell.Event>()
    var event: Observable<ExampleCell.Event> {
        return eventSubject
    }

3. Reactor+IGListKit

extension ExampleViewReactor: ListAdapterDataSource {
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        switch object {
        case is DiffableBox<ExampleModel>:
            let controller = ExampleSectionController()
            controller.event
                .subscribe(onNext: { [weak self] event in
                  switch event {
                    case .tapButton(let isSomething):
                        self?.action.onNext(.tapButton(isSomething))
                    }
                })
                .disposed(by: disposeBag)

4. Reactor

final class ExampleViewReactor: NSObject, Reactor {
    enum Action {
        case tapButton(Bool)
    }

    enum Mutation {
        case setIsSomething(Bool)
    }

    struct State {
        var isSomething: Bool
    }

    // MARK: - Properties

    var initialState: State

    let disposeBag = DisposeBag()

    // MARK: - Dependencies

    override init() {
        self.initialState = State(listVersion: 0, isSomething: true)
    }

    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .tapButton(let isSomething):
            return .just(.setIsSomething(isSomething))
        }
    }

    func reduce(state: State, mutation: Mutation) -> State {
        let state = state
        return state
    }
}