Hi all!
I’m modelling a sign in / registration form, where the form has two tabs to switch between sign in and registration. Both forms ask for email/password combo. Each tab has a button that changes label depending on the selected tab: “Sign In” or “Register”.
I found I had impossible states in my update
function. Here’s the story of how I removed the impossible states. I’d love for others to tell me if I’ve used a correct solution, or if there’s an even better solution.
The code before I found a solution was:
type alias Model =
{ formState : FormState
, authenticationState : AuthenticationState
}
type Msg
= SetEmail String
| SetPassword String
| ChangeFormState FormState -- switch between sign in / register
| RunSignIn -- launch the sign in XHR
| RunRegister -- launch the register XHR
| SignInResult (Result Http.Error SignInResponse)
| RegisterResult (Result Http.Error RegistrationResponse)
type AuthenticationState
= Unknown -- initial state
| InProgress -- during the XHR
| Failed -- bad username/password
| Authenticated JwtToken -- success!
type FormState
= FillingRegistrationForm RegistrationRequest
| FillingSignInForm SignInRequest
Given the above, I didn’t have much choice in update
to do something similar to this:
RunSignIn ->
case model.formState of
FillingSignInForm req ->
( { model | authenticationState = InProgress }
, sign_in req )
otherwise ->
( model, Cmd.none )
That otherwise
was a strong red flag that told me there was an impossible state here: it was possible for the model to have the sign in form, but to receive a message that said we’re going to register.
I found a very simple solution: I changed the RunSignIn and RunRegister messages to accept their corresponding requests:
type Msg
= -- ...
| RunSignIn SignInRequest
| RunRegister RegistrationRequest
This change provided me with two benefits that I could identify:
- I removed the impossible state and,
- The code to launch the XHRs is now simpler.
RunSignIn req ->
( { model | authenticationState = InProgress }
, sign_in req )
RunRegister req ->
( { model | authenticationState = InProgress }
, register req )
My original implementation was based on this model instead:
type alias Model =
{ email : Maybe String
, password : Maybe String
, authenticationState : AuthenticationState
}
type AuthenticationState
= Authenticated JwtToken -- successfully registered/signed in
| Anonymous -- initial state
| Authenticating -- during the XHR
| AuthenticationFailure -- bad email/password
When everything was a single state, I had this different impossible state instead:
RunSignIn ->
case ( model.email, model.password ) of
( Just email, Just pass ) ->
( { model | authenticationState = Authenticating }
, sign_in (AuthenticationRequest email pass)
)
otherwise ->
( model, Cmd.none )
That being said, the code was almost completely rewritten, but I did baby refactorings until the new model and messages emerged. I had a rough idea of where I was going, and leaned on Git & Elm to save and guide me throughout the process.
Anyway, just another experience report from me. As I said above, if you have a different solution, or know of one, I’d like to know about it. Now that I’ve implemented a solution, I’m ready to read other solutions. I’ll admit I haven’t done any research, in part because I like to learn by getting my hands dirty.
Take care!
François