Programming Phoenix LiveView Book Club!

After chapter 5 things are starting to gel. I’m now finding the ‘principle of least surprise’ applies:

  • "Hmm, I don’t like that validation error message. What if I add another parameter message: "my user friendly message? And it worked.
  • File upload functionality had some of the File API that looks a lot like Ruby’s, so again principle of least surprise
  • I can now work test-first more often. Tests for the Search page context were straight forward. Thanks to this forum I found the errors_on function in DataCase to write more readable and precise tests for error messages.
  • DRYing the validation for a 7 digit SKU was easy (extract function in Product and reuse that in ProductSearch.)

I skipped the image upload error handling exercise in Your Turn for now. I don’t need image uploads any time soon. It was fun to do the exercise in the chapter, as it gives more of a feel for handling state and working with files. Also, I want to skip ahead. I want to extract duplicate HTML in the search page. I copied the HTML from products/show and it looks like I need a function component for that, which is covered in the next chapter. Onward! :grinning:

Hmm. in Chapter 6, trying to get a type error when passing a number instead of a string as attribute value (as recommended on page 191 of the pdf):

<Component.hero content={123}>

The survey page happily shows 123 as heading instead of giving the promised error. I believe I wrote the type correctly:

attr :content, :string, required: true.

I am also not getting an error when omitting the required inner block.
Not sure what I am doing wrong. Suggestions?

Partial answer: the wrong type for ‘content’ is a warning, it did show up somewhere in my iex session. Time to update emacs so it shows warnings in .heex files better.

This stackoverflow post recommends to run mix clean. mix clean followed by mix test clearly shows the warning with little noise:

mix test
Compiling 60 files (.ex)
warning: attribute "content" in component PentoWeb.SurveyLive.Component.hero/1 must be a :string, got: 123
  lib/pento_web/live/survey_live.html.heex:2: (file)

Generated pento app
Finished in 0.8 seconds (0.3s async, 0.5s sync)
192 tests, 0 failures

Randomized with seed 126290

Still not sure why I don’t get a warning about the empty @inner_block.

<Component.hero content={123}></Component.hero>

Turns out an empty block is ok, to make it missing it has to be an open/close tag in one:

<Component.hero content={123}/>. Time to take a break :slight_smile: this was the sample code provided in the book… Good learning.

Decided to upgrade my emacs configuration, so that I could get compiler feedback when editing .heex.html files. After that, chapter 6 was wrapped up fairly quickly. I read ahead in chapter 7 and 8, the book continues to please with small steps and asking me questions about the trade-offs of various choices, and figuring out how things work under the hood, while at the same time working through what options for making things modular there are.

So what did you make of the Function Components chapter, if you had time for it?

Yes, I just finished it! I have been occupied with other stuff, but figured it was time to give this book some more, well deserved, attention!

Chapter 6

A chapter about getting more familiar with function components and organizing code. Some takeaways:

  1. I enjoyed the part about creating Query modules. Even tho the queries themselves were pretty simple, you got a good grasp of the project organization part of things. I was a bit unsure about having such a basic base() function on each query module, but after reading the argument about better refactoring, it made more sense!

  2. Good and thorough content about everything related to functional components. Attributes and slots, neat! There was even a good explanation about the mysterious :let attribute in the tables :col slot.

The feeling I get overall is that function components are quite simple to start out with, but there is a lot of depth and power to them, which one can see by browsing the CoreComponents module. :smiley:

1 Like


I want to share a problem I had with code given on chapter 2, page 56:

If I try root_layout: {PentoWeb.LayoutView, :root},, I get no "root" html template defined for PentoWeb.LayoutView (the module does not exist).

Changing to root_layout: {PentoWeb.Layouts, :root}, did work for me.

In chapter 3, page 69 when running the tests, there are 2 additional tests failing, when having done

If a logged in user visits the / route, make them redirect to the /guess route (chapter 2, page 61)

The failing tests are:

  1. test POST /users/log_in logs the user in (PentoWeb.UserSessionControllerTest)
    ** (RuntimeError) expected response with status 200, got: 302, with body:
    “You are being <a href="/guess">redirected.”
    code: response = html_response(conn, 200)
    (phoenix 1.7.11) lib/phoenix/test/conn_test.ex:373: Phoenix.ConnTest.response/2
    (phoenix 1.7.11) lib/phoenix/test/conn_test.ex:387: Phoenix.ConnTest.html_response/2
    test/pento_web/controllers/user_session_controller_test.exs:22: (test)

  1. test register user creates account and logs the user in (PentoWeb.UserRegistrationLiveTest)
    ** (RuntimeError) expected response with status 200, got: 302, with body:
    “You are being <a href="/guess">redirected.”
    code: response = html_response(conn, 200)
    (phoenix 1.7.11) lib/phoenix/test/conn_test.ex:373: Phoenix.ConnTest.response/2
    (phoenix 1.7.11) lib/phoenix/test/conn_test.ex:387: Phoenix.ConnTest.html_response/2
    test/pento_web/live/user_registration_live_test.exs:58: (test)

So, they have nothing to do with the product template generator, but are caused by the redirect. This also regards to

Add a migration and a field to give the User schema a username field, and
display that username instead of the email address when a user logs in.
Did you require the username to be unique?
(chapter 2, page 61)

As in the tests the appereance of the email is tested.

Maybe a hint that the tests have to be adopted, where the exercises are given, would be helpful?

1 Like

Consistent hints that tests have to be adopted would be helpfu indeedl. I thought there was a suggestion on devtalk already, but can’t find it. There are occasional suggestions in later chapters, but if one waits until then, there will be many other failing tests.
Because of duplication in generated tests, it is not that many fixes, but would be nicer to have regular reminders. I’ve adjusted to look at the tests at the end of each chapter, at the latest. I’ve also edited some tests to reduce duplicated steps.

1 Like

Chapter 7

In this chapter, we leveled up from function components to live components. The chapter does a good job in explaining what live components are and how they work. Some key takeaways:

  1. I think it was good that the chapter did a little explanation about the preload callback for loading data from the DB in one call instead of many. Even though we did not use it. Very good knowledge to have if the situation arises.
  2. The chapter really made a point about dividing the complexity into small chunks that can be worked on individually. I think the low friction involved in dividing a complex problem into simpler parts is one area where functional programming shines in general.
  3. CRC pattern is back again! :smiley:
  4. I find the mental model of having the live view working almost as a supervisor for its live components quite nice. Only handling the setup and then the messages from the components, doing quite little else.

Some notes

Firstly, when writing the heex for the demographic survey, we defined a phx-update property on the form. This was not mentioned again or handled in the chapter, leading to a runtime error when trying out the demographic form. It was quite an easy fix though, drawing inspiration from previous code:

  def handle_event("validate", %{"demographic" => demograpic_params}, %{assigns: %{demographic: demographic}} = socket) do
    changeset = 
      |> Survey.change_demographic(demograpic_params)
      |> Map.put(:action, :validate)
    {:noreply, assign_form(socket, changeset)}

Secondly, I noticed that sometimes a code for a symbol would be used &#x2605, and sometimes the symbol itself ★. In this case, I opted for just using the symbol directly.

Finally, when doing the second “Give it a try” exercise, in order to render any error below the rating option input, and still keeping the save button to the right, I inserted the .error function component into the rating form like so:

    <.button phx-disable-with="Saving...">Save</.button>
    <.error :for={{msg, _} <- @form[:stars].errors}><%= msg %></.error>

but I’m not sure if this is the best way and was wondering if anyone has another solution? :slight_smile:

1 Like