Skip to content

return state from .submitting state doesn't work #10

@Sadmansamee

Description

@Sadmansamee

Hi @V8tr
I'm returning a state on .submitting state for a particular event, but it doesn't return the expected state, here's my code, what am I doing wrong here?

here on .submitting state from an API call I'm calling onSignedIn event, but even after calling return .signedIn(token) , II am not getting this state case let .signedIn(token)

Here's the shortcode

        case .submitting:
            switch event {
            case let .onFailedToLogin(error):
                return .error(error)
            case let .onSignedIn(token):
                return .signedIn(token)
            default:
                return state
            }

Here's the full code

class LoginViewModel: LoginViewModelProtocol, Identifiable {
    let authRepository: AuthRepository

    @Published var phoneNumber: String = ""
    @Published var password: String = ""
    @Published var isValid: Bool = false
    @Published var phoneNumberError: String?
    @Published var passwordError: String?

    private var bag = Set<AnyCancellable>()
    private let input = PassthroughSubject<Event, Never>()

    @Published private(set) var state = State.initial
    @Published var phoneNumberValidator = PhoneNumberValidation.empty
    @Published var passwordValidator = PasswordValidation.empty

    @Inject var router: Router

    init() {
        authRepository = AuthRepository()

        Publishers.system(
            initial: state,
            reduce: reduce,
            scheduler: RunLoop.main,
            feedbacks: [
                whenSubmitting(),
                Self.userInput(input: input.eraseToAnyPublisher())
            ]
        )
        .assign(to: \.state, on: self)
        .store(in: &bag)

        Publishers.CombineLatest(validPhoneNumberPublisher, passwordValidatorPublisher)
            .dropFirst()
            .sink { _emailError, _passwordValidator in
                self.isValid = _emailError.errorMessage == nil &&
                    _passwordValidator.errorMessage == nil
                if self.isValid {
                    self.send(event: .onSubmitable)
                }
                print("LoginViewModel combine \(self.state)")
            }
            .store(in: &bag)

        validPhoneNumberPublisher
            .dropFirst()
            .sink { _error in
                self.phoneNumberError = _error.errorMessage
            }
            .store(in: &bag)

        passwordValidatorPublisher
            .dropFirst()
            .sink { _error in
                self.passwordError = _error.errorMessage
            }
            .store(in: &bag)
    }

    deinit {
        bag.removeAll()
    }

    func send(event: Event) {    
        input.send(event)
    }

    private var validPhoneNumberPublisher: AnyPublisher<PhoneNumberValidation, Never> {
        $phoneNumber
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .removeDuplicates()
            .map { _value in
                if _value.isEmpty {
                    return .empty
                } else if !_value.isPhoneNumber() {
                    return .inValidPhoneNumber
                } else {
                    return .validPhoneNumber
                }
            }
            .eraseToAnyPublisher()
    }

    private var passwordValidatorPublisher: AnyPublisher<PasswordValidation, Never> {
        $password
            .removeDuplicates()
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .map { password in
                if password.isEmpty {
                    return .empty
                } else {
                    return passwordStrengthChecker(forPassword: password)
                }
            }
            .eraseToAnyPublisher()
    }
}

// MARK: - Inner Types

extension LoginViewModel {
    enum State {
        case initial
        case submitable
        case submitting
        case signedIn(OauthToken)
        case error(HttpError)
    }

    enum Event {
        case onStart
        case login
        case onSubmitable
        case onSignedIn(OauthToken)
        case onFailedToLogin(HttpError)
    }
}

// MARK: - State Machine

extension LoginViewModel {
    
    private func handleError(error: HttpError) {
        switch error {
        case let .errors(apiError):
            if let errors = apiError.errors {
                for error in errors {
                    if error.key == "invalid_resource_owner_password" {
                        passwordError = error.message
                    }
                }
            }

        default:
            break
        }
    }
    
    func reduce(_ state: LoginViewModel.State, _ event: LoginViewModel.Event) -> LoginViewModel.State {

        switch state {
        case .initial:
            switch event {
            case .onSubmitable:
                return .submitable

            default:
                return state
            }
        case .submitting:
            switch event {
            case let .onFailedToLogin(error):
                handleError(error: error)
                return .error(error)
            case let .onSignedIn(token):
                return .signedIn(token)
            default:
                return state
            }
        case let .signedIn(token):
            return state
        case let .error(error):
            handleError(error: error)
            return state
        case .submitable:
            switch event {
            case .login:
                return .submitting
                
            default:
                return state
            }
        }
    }

    func whenSubmitting() -> Feedback<State, Event> {
        Feedback { (state: State) -> AnyPublisher<Event, Never> in
            guard case .submitting = state else { return Empty().eraseToAnyPublisher() }

            return self.authRepository
                .login(phoneNumber: self.phoneNumber, password: self.password)
                .map(Event.onSignedIn)
                .catch { Just(Event.onFailedToLogin($0)) }
                .eraseToAnyPublisher()
        }
    }

    static func userInput(input: AnyPublisher<Event, Never>) -> Feedback<State, Event> {
        Feedback { _ in input }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions