diff --git a/adopt-liveview-notes.md b/adopt-liveview-notes.md index f8459253..3145dd46 100644 --- a/adopt-liveview-notes.md +++ b/adopt-liveview-notes.md @@ -1,38 +1,41 @@ -# Learn Phoenix LiveView +# Phoenix LiveView Notes -These notes are from the extremely useful +These notes are an expansion on the extremely useful [Adopt Liveview](https://adopt-liveview.lubien.dev/) course by Lubien Dev. -If you want deeper explanations and details then definitely check it out! -The aim of this file is to provide a fast understanding -of some **core components** of LiveView. +Our is to capture our own understanding +of some **core components** of `LiveView`. -# 1. Fundamentals +## 1. Fundamentals -## The PageLive module +### The PageLive module -To begin, all the code for our LiveView must be contained inside a module: +To begin, all the code for our `LiveView` must be contained inside a module: ```elixir defmodule PageLive do use OurProjectWeb, :live_view do ``` -We can name `PageLive` anything, but we normally add "Live" to the end of the + +We can name `PageLive` anything, +but we normally add "Live" to the end of the module name to indicate that this module is a LiveView. -Next, we see the line `use OurProjectWeb, :live_view`. -+ The [`use`](https://hexdocs.pm/elixir/macros.html) macro is in all LiveViews +Next, we see the line `use OurProjectWeb, :live_view`. ++ The [`use`](https://hexdocs.pm/elixir/macros.html) + macro is in all LiveViews + `use` executes code at compile time -+ `live_view` indicates that this module is a LiveView ++ `:live_view` indicates that this module is a `LiveView` (will see more of how this works in routers) -## Putting the 'View' in LiveView -The most basic of LiveViews will contain at minimum a `render/1` function, -which renders HTML like code called `HEEx` (more on that later). -It looks a little something like this: +### Putting the 'View' in LiveView + +The most basic `LiveView` contains a `render/1` function, +which renders `HTML` like code called `HEEx` (more on that later). +It looks a little something like this: -```elixir +```elixir def render(assigns) do ~H""" Hello World! @@ -40,42 +43,46 @@ def render(assigns) do end ``` -Super basic! Here we're passing in an assigns (which we're about to examine), -and we contain our `HEEx` code inside the +Super basic! Here we're passing in an assigns +(which we're about to examine), +and we contain our `HEEx` code inside the `~H` [`sigil_H/2`](https://hexdocs.pm/phoenix_live_view/0.17.0/Phoenix.LiveView.Helpers.html#sigil_H/2S). > In Elixir, [sigils](https://hexdocs.pm/elixir/sigils.html) -> are binary functions that essentially transform text into something else +> are binary functions that essentially transform text into something else So why are we passing something called `assign` into our function? -Well, most the time we will want to manage state in our LiveView. +Most of the time we manage state in our `LiveView`. This requires us to **store** state somewhere, -which leads us to `assigns`. -> _Note: the render function must only and always be -> passed the argument `assigns`_ +which is done via the `assigns` map. + +> **Note**: the render function must always be passed `assigns`. -## LiveView assigns +## `LiveView` Assigns -### Storing state -In frontend frameworks we need a way to store state -(e.g in `hooks` in ReactJS). -In LiveView, we use +### Storing State + +In frontend frameworks we need a way to store state +(e.g in `hooks` in `React`). +In `LiveView`, we use [`assigns`](https://hexdocs.pm/phoenix_live_view/assigns-eex.html): -+ All LiveView data is stored in the ++ All `LiveView` data is stored in the [`socket`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Socket.html) data struct + Your own data is stored under the `assigns` key of said socket + (i.e, `assigns` is an [elixir map](https://hexdocs.pm/elixir/1.12/Map.html)) + In assigns, you can store any variable (lists, maps, structs, etc..) -To make use of `assigns`, we normally need to use -the `mount/3` callback function. +To make use of `assigns`, we use +the `mount/3` callback function. + +### The `mount/3` callback -### The `mount/3` callback -LiveView sends information via **callbacks** ( _functions that run when an **event** happens_). +`LiveView` sends information via **callbacks** +(_functions that run when an **event** occurs_). -The `mount/3` callback runs when the LiveView is initialized. +The `mount/3` callback runs when the `LiveView` is initialized. It takes three arguments: @@ -85,26 +92,27 @@ def mount(_params, _session, socket) do {:ok, socket} end ``` -We'll talk about the socket next, -but for now let's break down the parameters being passed in: -+ `params` are parameters coming from the URL +The arguments being passed in are: + ++ `params` are parameters coming from the URL (e.g. `/users/:id` where `:id` would be one of the params) + `session` is data from the current browsing session, useful for authentication + `socket` is our socket data struct that we just spoke about, it contains data from the current session and holds the `assigns` map -> Note: as we didn't use `params` or `session` in our code above we let -> phoenix know this with the underscore: `_params` +> **Note**: as `params` or `session` are both unused in the code above we let +> `Phoenix` know this with the underscore; `_params` & `_session`. Notice that the `mount/3` returns the tuple `{:ok, socket}` if successful. -+ The `:ok` lets the system know that the mount was successful + ++ The `:ok` lets the system know that the mount was successful + The `socket` gives us access to the new data ### The `%Socket{}` data struct -State management revolves around modifying state of the `socket`. +State management revolves around modifying state of the `socket`. Let's use the [`.dbg/2`](https://hexdocs.pm/elixir/debugging.html#dbg-2) macro to examine what happens to our assigns map when we modify the socket: @@ -116,28 +124,29 @@ def mount(_params, _session, socket) do {:ok, socket} end ``` + > Notice that we have to declare a new socket to store the new data? -> This is because data in Elixir is immutable. +> This is because data in Elixir is immutable. The first debug message returns the unaltered `assigns`: -``` -socket.assigns #=> %{__changed__: %{}, flash: %{}, live_action: :index} +```elixir +socket.assigns => %{__changed__: %{}, flash: %{}, live_action: :index} ``` -And the next returns the modified socket with the new `assigns`: +And the next returns the modified socket with the new `assigns`: -``` -socket.assigns #=> %{name: "R2-D2", __changed__: %{name: true}, f -lash: %{}, live_action: :index} +```elixir +socket.assigns => %{name: "R2-D2", __changed__: %{name: true}, + flash: %{}, live_action: :index} ``` -As we can see, assigns are just a map with some data about your LiveView. -Let's look at its components: +`assigns` is just a map with some data about the `LiveView`. +In this case it contains: + `name` is the map we added -+ `__changed__` is a map to explain updates to HTML rendering engine -+ `flash` is a map to send info, success and alert messages to the user ++ `__changed__` is a map to explain updates to `HTML` rendering engine ++ `flash` is a map to send info, success and alert messages to the client + `live_action` use this data to know where we are in the application (we will see more of this when covering routers) @@ -145,7 +154,8 @@ Ok, so we've seen how data is stored and what its stored in. Let's look at how we were render that data. ### Rendering `assigns` -We render assigns in LiveView using tags: `<%= %>`. + +We render assigns in `LiveView` using tags: `<%= %>`. ```elixir def render(assigns) do @@ -156,15 +166,15 @@ end ``` Here we're accessing the `name` key from the assigns map using `@name`. -This is exactly the same as `assigns.name`. +This is exactly the same as `assigns.name`. -If we stored an name in assigns when we used the `mount/3` function to +If we stored an name in assigns when we used the `mount/3` function to declare a socket, then the code above would render the name, e.g. "Hello R2-D2". +### Recap -### Recap -We now understand the following: +We now understand the following: ```elixir @@ -193,13 +203,16 @@ end ## Modifying state with events ### How do they work? -For each event in your HEEx, you need a corresponding event handler function: + +For each event in your HEEx, +there is a corresponding event handler function: ```elixir def handle_event("your_event", _params, socket) ``` It takes: + + The name of the event you wish to handle + The event parameters + The state of the Socket of the current user @@ -207,15 +220,15 @@ It takes: It expects the return of `{:noreply, socket}`, which is basically saying "Everything is ok! Here is the initial socket" -> _(Similar to `mount/3`'s `{:ok, socket}`, but `mount/3` followers +> _(Similar to `mount/3`'s `{:ok, socket}`, but `mount/3` followers > the Elixir pattern)_ Let's see it in action. ### The `phx-click` event -Following the pattern of events in the HEEx code, and handling the -event in a corresponding function we have: +Following the pattern of events in the HEEx code, and handling the +event in a corresponding function we have: ```elixir defmodule PageLive do @@ -243,55 +256,56 @@ defmodule PageLive do end ``` -So whats going on here? +So whats going on here? -In Phoenix, `phx-click` generates an event of your choosing when the element -is clicked (shocking, I know). +In `Phoenix`, `phx-click` generates an event of your choosing when the element +is clicked. Let's break down a use case of the code above: -1. The LiveView is initialized, calling `mount/3` and passing the assigns +1. The `LiveView` is initialized, calling `mount/3` and passing `assigns` with our data `name` to the socket 2. The web page displays "Hello R2-D2" -3. The user clicks on the button, triggering our `phx-click` which generates +3. The person clicks on the button, triggering our `phx-click` + which generates event with the name `reverse` -4. Our `handle_event/3` callback is activated, which reverses the string +4. Our `handle_event/3` callback is activated, which reverses the string stored in our assign and stores it in a new socket struct 5. The `handle_event/3` returns the new socket 6. The webpage displays "Hello 2D-2R" -With barely any code, we've triggered and handle an event, and re-rendered the -page. Elegant right? +With barely any code, we've triggered and handle an event, +and re-rendered the page. Elegant right? ### Recap + + Adding `phx-click="event_name"`triggers event when clicked + For each event on HEEx, you need a corresponding `handle_event/3` callback -+ The `mount/3` callback returns `{:ok, socket}` ++ The `mount/3` callback returns `{:ok, socket}` + The `handle_event/3` returns `{:noreply, socket}` -
-
-
+## 2. `HEEx` -# 2. HEEx +### `HEEx` is _Not_ `HTML` -## HEEx is not HTML -The `sigil_H` in the `render` function returns a data struct called HEEx. -It is the Phoenix template language, HTML + EEX, where EEx is Embedded Elixir, -an Elixir template engine. +The `sigil_H` in the `render` function returns a data struct called `HEEx`. +It is the `Phoenix` template language, `HTML + EEX`, +where `EEx` is _Embedded_ `Elixir`, +an `Elixir` template engine. > Optimized to know when something has been modified based on its assigns > and sends the minimum amount of data form server to client `assigns` is just one of its superpowers. -## HEEx basics -Some basic rules of HEEx are as follows: +## `HEEx` Basics + +Some basic rules of HEEx are as follows: -+ Using the `<%= %>` tag renders Elixir code that is ++ Using the `<%= %>` tag renders Elixir code that is [Phoenix.HTML.safe](https://hexdocs.pm/phoenix_html/Phoenix.HTML.Safe.html) -+ Using the `<% %>` tag executes elixir code but does not render anything -+ `nil` values do not render ++ Using the `<% %>` tag executes elixir code but does not render anything ++ `nil` values do not render Seeing this in action: @@ -310,17 +324,17 @@ Case by case renders: 1. The string "R2-D2" 2. The integer 2 -3. The third case just uses the string concatenation operator -<> whose result is "C-3PO, Human cyborg relations". +3. The third case just uses the string concatenation operator `<>` +whose result is "C-3PO, Human cyborg relations". 4. Nothing! Because of the tags do not include the `=` sign, -the code is executed and because of `IO.puts` you can see the result -in the terminal +the code is executed and because of `IO.puts` you can see the result +in the terminal. -## Conditional Rendering +## Conditional Rendering ### Using `if-else` for simple cases -```elixir +```elixir ~H"""
<%= if @need_id? do %> @@ -333,14 +347,15 @@ in the terminal ``` Notice: + + Need the `=` for the `if` tag + The `else` and `end` tags don't need `=` -+ We're getting the assign with `@` -+ In Elixir we display booleans with `boolean?` ++ We're getting the `assign` with `@` ++ In `Elixir` we display booleans with `boolean?` -If no `else` block is needed, can simply omit it. +If no `else` block is needed, can simply omit it. -But there is a better way.. +But there is a better way ... ### The special `:if` attribute @@ -353,11 +368,14 @@ directly inside a HTML tag: """ ``` +Ref: +https://adopt-liveview.lubien.dev/guides/conditional-rendering/en#the-special-attribute-if + ### Using `case` for complex cases -Elixir doesn't have `else-if`, so instead we use `case`. +Elixir doesn't have `else-if`, so instead we use `case`. -Let's look at a full LiveView module to see this in action: +Observe a full `LiveView` module to see this in action: ```elixir defmodule PageLive do @@ -402,13 +420,13 @@ end This is showcasing a combination of previous topics as well `case`. To go over what we've seen before: -+ Our assign is `tab` and initialized as "home" ++ The `assign` is `tab` and initialized as "home" and given to the socket with `mount/3` + Using `phx-click="show_{tab_name}"` -+ Our `handle_event/3` can use ++ Our `handle_event/3` can use [pattern matching](https://hexdocs.pm/elixir/pattern-matching.html) -and string concatenation to change the assigns all in one function -+ We can use HTML `disabled` property to disable a button +and string concatenation to change the assigns all in one function ++ We can use `HTML` `disabled` property to disable a button if we're on the correct tab Let's now talk about the `case`: @@ -418,15 +436,14 @@ Let's now talk about the `case`: + Each condition we do `<% "expected value" -> %>` + Note: We can add a default clause with `<% _ -> %>` - ### Condition chaining with `cond` When rendering something based on a condition that is not about equality we use `cond` which follows the logic: + Each clause returns `true` or `false` -+ First condition that returns true ends the flow -and renders the prescribed html ++ First condition that returns true ends the flow +and renders the prescribed html + To add a standard clause add `true ->` at the **end** For example: @@ -458,16 +475,16 @@ For example: ``` ### Recap + + For `if-else` use `<%= if condition do %>` and `<% else %>` + For only `if` use special if-attribute `:if={condition}` in html tag + For multiple comparisons of the same variable use `<%= case value of %>` -+ For multiple conditions that don't involve comparing equality -use `<%= cond do %>` -+ In all cases, have the `=` in the **first** tag - ++ For multiple conditions that don't involve comparing equality +use `<%= cond do %>` ++ In all cases, have the `=` in the **first** tag -## List rendering +## List rendering ### Using `for` comprehension and `:for` attribute @@ -505,7 +522,7 @@ These can solved with streams. Phoenix's efficient way to handle large (or infinite) lists. -> Note: In the following code our assign looks clunky since +> Note: In the following code our assign looks clunky since > we have to include an id to use streams. >In reality, this is not an issue as if the data is from a database > an id will be included. @@ -539,39 +556,35 @@ end Let's break it down: -+ We use the ++ We use the [`stream/4`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4) function to define a stream + `stream/4` recieves our socket, the name of the stream as an atom and the initial value -+ Our unordered list (or any parent element of the list) must have ++ Our unordered list (or any parent element of the list) must have a unique id, in this case `food-stream` + Must add `phx-update="stream"` to parent element (to define that children are part of a stream) -+ Use special assign `@streams.food`, every time a stream is created ++ Use special assign `@streams.food`, every time a stream is created with `:some_name` you generate a special assign `@streams.some_name` + `:for` now loops with two elements inside a tuple. The id is useful when wanting to update / delete elements -### Recap +### Conclusion -+ Combining `for` comprehension and special `:for` attribute ++ Combining `for` comprehension and special `:for` attribute provides simple, readable rendering + LiveView provides efficient rendering for large or infinite data using streams -
-
-
- -# 3. Events +## 3. Events -## Using phx-value +### Using `phx-value` -In the accuracy example, what if we want to include a button +In the accuracy example, what if we want to include a `