New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dealing with form(s) state #422
Comments
I think your approach is quite ok. I can share some thoughts but note that I don't think you're doing anything wrong.
For the last point, see that you can have more than one store in your app. You can have one locally for the form and its draft state, and then use the main one when it's finished. |
I've approached this by creating a An example would be something like: Actions struct ResetPasswordRequest: Action, HasForm { var props: String }
struct ResetPasswordSuccess: Action, HasForm { }
struct ResetPasswordFailure: Action, HasError, HasForm { var payload: String } FormState enum AppForm: CaseIterable {
case resetPass
var key: String {
switch self {
case .resetPass: return "ResetPassword"
}
}
}
struct FormState: StateType {
var forms: [AppForm: FormSubState] = [:]
}
struct FormSubState: StateType {
var pending: Bool = false
var success: Bool = false
var error: String?
}
extension FormState: Hashable {}
extension FormSubState: Hashable {} Reducer func formReducer(_ action: Action, _ state: FormState?) -> FormState {
var state = state ?? FormState()
guard action is HasForm else { return state }
let requestRgx = "^(.+)REQUEST$"
let successRgx = "^(.+)SUCCESS$"
let failureRgx = "^(.+)FAILURE$"
let str = String(describing: type(of: action))
func mapActionToForm(_ action: String) -> AppForm {
guard let form = AppForm.allCases.first(where: { $0.key == action }) else { fatalError("Could not find form for action type \(action)") }
return form
}
if requestRgx.test(str) {
let matches = requestRgx.matches(str)
state.forms[
mapActionToForm(matches[1])
] = .init(pending: true, success: false, error: nil)
return state
}
if successRgx.test(str) {
let matches = successRgx.matches(str)
state.forms[
mapActionToForm(matches[1])
] = .init(pending: false, success: true, error: nil)
return state
}
if failureRgx.test(str) {
let error = (action as? HasError)?.payload
let matches = failureRgx.matches(str)
state.forms[
mapActionToForm(matches[1])
] = .init(pending: false, success: false, error: error)
return state
}
if action is ResetFormState {
let state = FormState()
return state
}
return state
} This allows me to keep the reducer logic from growing and growing as I had more form states, instead I simply conform a form based action to I can now select my form state using an enum |
I'm curios how people are handing form states during reducer state changes.
For example, I have a login form that takes 2 fields
username
andpassword
.On submission I dispatch an action
store.dispatch(LoginRequest(payload: .init(username: username, password: password)))
I have middleware that picks up this action and makes an async request to my auth provider.
On success or failure I dispatch the appropriate action
dispatch(LoginRequestSuccess(payload: .init(accessToken: "123", refreshToken: "456")))
or
dispatch(LoginRequestFailure(payload: .init(some_error)))
Currently my reducer looks something like this:
Based on
state.form
in my view controller I disable the form, show a loading indicator, show an error alert or reset the form and trigger a navigation.This works really, however I have a few concerns:
authenticationReducer
should really only be concerned with auth state, not ui concerns such as form stateI have numerous forms related to login / auth - reset password, recover username, trigger multi factor and so on, these additional states will all cause my reducer to grow and grow
My actions become tied to my forms, should I create an action to refresh my tokens, I know need to pretty much duplicate
LoginRequestSuccess
to set new tokens in my store. Ideally I'd like my actions to be as generic and flexible as possible.Coming from the React / Redux world I used Redux Form quite heavily, this let me keep track of form state in it's own separate reducer and provided callbacks such as
onSubmitSuccess
andonSubmitFail
which allowed me to keep my ui state within the ui layer not mix my auth and form states.The text was updated successfully, but these errors were encountered: