Real World Phoenix |> Sign Up Flow SPA style with LiveView!
Published: 2021-02-01 09:47:20

---
title: Real World Phoenix |> Sign Up Flow SPA style with LiveView!
published: true
date: 2019-12-18 00:00:00 UTC
tags: 
canonical_url: https://www.kabisa.nl/tech/real-world-phoenix-sign-up-flow-spa-style-with-liveview/
---

In the [last post](https://www.theguild.nl/real-world-phoenix-user-roles-the-explicit-way/) we configured our app to be able to handle sign up of different user roles. In this post we’ll explore how we can let these different types of users register for an account.

While you were not watching I have added the Bulma css framework to our app and we’ll use that to create a registration page that has 2 tabs. one for student registration and one for teacher registration. Bulma doesn’t come with any javascript included. So if we want to get a more SPA feel to our sign up flow, we’d have to add some js sprinkles. This is where LiveView steps in! We can create a snappy SPA feel without having to write any javascript at all! Let’s explore how this would work.

In this project I had LiveView enabled already. If you need to add LiveView to your project I’d advise you to check out the [documentation here](https://hexdocs.pm/phoenix_live_view/installation.html#content), as always it is very clear so that’ll get you up and running quickly.

I have played with the idea of only using LiveView to toggle the tabs for these sign-up types, but that seems a bit silly because we could just use anchor tags for that. So let’s go with a full LiveView form! This also gives us a couple of benefits. The first being that we can have live form validation when a user starts filling in the form. The second benefit is that we can call our backend directly to process the sign-up. As we are using Pow for our user registration, it is a bit more work to hook into the signup process that is managed by Pow. In the end of this article I’ll reference the way to do this with Pow’s callbacks. If we use a LiveView form we can bypass the controller flow totally and just use our live component to target the sign-up method needed based on the type of registration.

With that said, let’s look at how we can put together our LiveView form:We’ll create two tabs that will switch between student and teacher signup and use the ‘phx-click’ binding to switch between these two types.

```

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<div class="tabs is-boxed">
  <ul>
    <li class="<%= if @type == "student", do: "is-active"%>">
      <a>
        <span phx-click="switch-type-student">Student Registration</span>
      </a>
    </li>
    <li class="<%= if @type != "student", do: "is-active"%>">
      <a>
        <span phx-click="switch-type-teacher">Teacher Registration</span>
      </a>
    </li>
  </ul>
</div>

```

I have introduced a `@type` value that we’ll use to switch between the types of registration. It’s used to show the selected tab and also to show the fields for student vs teacher registration, currently that is only a teacher biography. We store the specifics for a student/teacher in a separate table that is associated with the user account. Here we can use the `inputs_for` helper to add associated fields to our form as you can see below:

```

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

 <%= if @type == "student" do %>
   <%= inputs_for f, :student_profile, fn fp -> %>
     <div class="field">
       <%= label fp, :first_name, class: "label" %>
       <%= text_input fp, :first_name, class: "input" %>
       <%= error_tag fp, :first_name %>
     </div>

     <div class="field">
       <%= label fp, :last_name, class: "label" %>
       <%= text_input fp, :last_name, class: "input" %>
       <%= error_tag fp, :last_name %>
     </div>
   <% end %>
 <% else %>
   <%= inputs_for f, :teacher_profile, fn fp -> %>
     <div class="field">
       <%= label fp, :first_name, class: "label" %>
       <%= text_input fp, :first_name, class: "input" %>
       <%= error_tag fp, :first_name %>
     </div>

     <div class="field">
       <%= label fp, :last_name, class: "label" %>
       <%= text_input fp, :last_name, class: "input" %>
       <%= error_tag fp, :last_name %>
     </div>

     <div class="field">
       <%= label fp, :bio, class: "label" %>
       <%= textarea fp, :bio, class: "input" %>
       <%= error_tag fp, :bio %>
     </div>
   <% end %>
 <% end %>

```

In our backend we’ll need to mount a LiveView component that will handle these interactions. We’ll create a file called `lib/student_manager_web/live/user_registration.ex`.

The LiveView behaviour requires you to implement two callbacks. `render/1` to render the actual html and `mount/2` that is used to initialize some state when the component is mounted.

In the `render/1` function we’ll render our `.leex` template file that contains our form.

```

1

def render(assigns), do: Phoenix.View.render(StudentManagerWeb.Pow.RegistrationView, "new.html", assigns)

```

Here is the full contents of our .leex file:

```

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

<%= f = form_for @changeset, "#", [as: :user, phx_change: :validate, phx_submit: :save] %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

   <div class="tabs is-boxed">
      <ul>
        <li class="<%= if @type == "student", do: "is-active"%>">
          <a>
            <span phx-click="switch-type-student">Student Registration</span>
          </a>
        </li>
        <li class="<%= if @type != "student", do: "is-active"%>">
          <a>
            <span phx-click="switch-type-teacher">Teacher Registration</span>
          </a>
        </li>
      </ul>
    </div>
    <%= if @type == "student" do %>
      <%= inputs_for f, :student_profile, fn fp -> %>
      <div class="field">
        <%= label fp, :first_name, class: "label" %>
        <%= text_input fp, :first_name, class: "input" %>
        <%= error_tag fp, :first_name %>
      </div>

      <div class="field">
        <%= label fp, :last_name, class: "label" %>
        <%= text_input fp, :last_name, class: "input" %>
        <%= error_tag fp, :last_name %>
      </div>
      <% end %>
    <% else %>
      <%= inputs_for f, :teacher_profile, fn fp -> %>
      <div class="field">
        <%= label fp, :first_name, class: "label" %>
        <%= text_input fp, :first_name, class: "input" %>
        <%= error_tag fp, :first_name %>
      </div>

      <div class="field">
        <%= label fp, :last_name, class: "label" %>
        <%= text_input fp, :last_name, class: "input" %>
        <%= error_tag fp, :last_name %>
      </div>

      <div class="field">
        <%= label fp, :bio, class: "label" %>
        <%= textarea fp, :bio, class: "input" %>
        <%= error_tag fp, :bio %>
      </div>
      <% end %>
    <% end %>

  <div class="field">
    <%= label f, Pow.Ecto.Schema.user_id_field(@changeset), class: "label" %>
    <div class="control">
      <%= text_input f, Pow.Ecto.Schema.user_id_field(@changeset), class: "input" %>
    </div>
    <%= error_tag f, Pow.Ecto.Schema.user_id_field(@changeset) %>
  </div>

  <div class="field">
    <%= label f, :password, class: "label" %>
    <%= password_input f, :password, value: input_value(f, :password), class: "input" %>
    <%= error_tag f, :password %>
  </div>

  <div class="field">
    <%= label f, :confirm_password, class: "label" %>
    <%= password_input f, :confirm_password, value: input_value(f, :confirm_password), class: "input" %>
    <%= error_tag f, :confirm_password %>
  </div>
  <div>
    <%= submit "Register", phx_disable_with: "Saving...", class: "button is-link" %>
  </div>
</form>

```

So that is pretty straightforward. Now let’s create the `mount/2` function and see how we can make the form interactive.On mount, we’ll assign the `type` variable the “student” value, so we are defaulting to the student sign-up.

```

1
2
3
4
5
6
7

def mount(_session, socket) do
  {:ok,
    assign(socket, %{
          changeset: Accounts.User.changeset(%User{}, %{}),
          type: "student"
    })}
end

```

To switch the sign-up form and thus the `type` value to “teacher”, we just need to add these callback functions to our module.

```

1
2
3
4
5
6
7

def handle_event("switch-type-student", _path, socket) do
  {:noreply, assign(socket, type: "student")}
end

def handle_event("switch-type-teacher", _path, socket) do
  {:noreply, assign(socket, type: "teacher")}
end

```

Once we update the `type` value in our assigns, the component will automatically re-render by calling the `render/1` function.

Now it’s time to create a relationship that will store the student and teacher data. We’ll create a Studentprofile and a TeacherProfile. We can define exactly what we want to store there.

These two migrations should do the trick for our database:

```

1
2
3
4
5
6
7
8
9
10
11
12
13
14

defmodule StudentManager.Repo.Migrations.AddTeacherProfile do
  use Ecto.Migration

  def change do
    create table(:teacher_profiles) do
      add :first_name, :string
      add :last_name, :string
      add :bio, :string
      add :user_id, references(:users)

      timestamps()
    end
  end
end



1
2
3
4
5
6
7
8
9
10
11
12
13
14

defmodule StudentManager.Repo.Migrations.AddStudentProfile do
  use Ecto.Migration

  def change do
    create table(:student_profiles) do
      add :first_name, :string
      add :last_name, :string
      add :instrument, :string
      add :user_id, references(:users)

      timestamps()
    end
  end
end

```

In the form page above you had already seen the inputs\_for helper that added these fields to our form. So how do we store these associations when we create our user. Fortunately `Ecto` makes this really straightforward! We’ll use `cast_assoc` that will use our Teacher and/or StudentProfile changeset method to store the associated struct.

Here is the implementation added to our sign up changesets

```

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def teacher_registration_changeset(user_or_changeset, attrs) do
  user_or_changeset
  |> Repo.preload(:teacher_profile)
  |> changeset(attrs)
  |> cast_assoc(:teacher_profile)
  |> change(%{roles: ["teacher"]})
end

def student_registration_changeset(user_or_changeset, attrs) do
  user_or_changeset
  |> Repo.preload(:student_profile)
  |> changeset(attrs)
  |> cast_assoc(:student_profile)
  |> change(%{roles: ["student"]})
end

```

The last part of our puzzle is creating the callbacks for our LiveView Form. The form helper takes two options which define the callback functions being called in our LiveView component.

```

1

<%= f = form_for @changeset, "#", [as: :user, phx_change: :validate, phx_submit: :save] %>

```

So `phx_change` and `phx_submit` will call our handlers on any change and submit respectively. Note that his means that our validate callback will be called on every change in the form. That is a lot of requests, because it happens per keystroke basically. This is running on a websocket connection so for now this is fine and if we do want to limit this we can use the awesome ratelimiting features that have been added to Phoenix LiveView, `phx-debounce` and `phx-throttle`. Read more about that [here](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-rate-limiting-events-with-debounce-and-throttle).

And here are the function definitions in our LiveView component:

```

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

def handle_event("validate", %{"user" => params}, socket) do
  changeset =
    %User{}
    |> StudentManager.Accounts.User.changeset(params)
    |> Map.put(:action, :insert)

  {:noreply, assign(socket, changeset: changeset)}
end

def handle_event("save", %{"user" => user_params}, socket) do
  case Accounts.create_user(user_params) do
    {:ok, user} ->
      {:stop,
        socket
        |> put_flash(:info, "user created")
        |> redirect(to: "/")}

    {:error, %Ecto.Changeset{} = changeset} ->
      {:noreply, assign(socket, changeset: changeset)}
  end
end

```

That should make both the user and teacher sign up work as expected. Hope you learned something new from this post. Next up is setting up email sending and making sure people get a nice welcoming email when they sign up for our app.