How does `update/3` work in Phoenix LiveView?

I just watched the wonderful video by Chris McCord about how to write a Twitter Clone with Phoenix LiveView within 15min (https://www.youtube.com/watch?v=MZvmYaFkNJI).

At around 11:45 he talks about how to update the struct rendered in a LiveComponent and he uses 3 mechanism to make this work. It is a bit unclear to me how Phoenix LiveView handles the updates under the hood and would much appreciate if somebody could explain it to me. So here are the mechanisms:

  1. Use the update/3 function to prepend the updated post to the posts assigned to the socket.
{:noreply, update(socket, :posts, fn posts -> [post | posts] end)}

My question here is: How does Phoenix LiveView know which LiveComponent needs to be updated? I’m confused by the [post | posts] part of the function since posts is going to be an empty list after the initial render (see Mechanism 2). So, why is it necessary to prepend the post to posts?

  1. Set posts to be temporary_assigns.
{:ok, assign(socket, :posts, fetch_posts()), temporary_assigns: [posts: []])}

To my understanding, this clears the assigned posts from memory once the initial rendering has succeeded, after the LiveView is “hydrated” on the second render after the websocket connection is established.

  1. Set the posts DOM container to phx-update="prepend"
<div id="posts" phx-update="prepend">

As I understand this code, LiveView is going to prepend any new posts to the list of LiveComponents, basically rendering any new posts at the top of the list of existing posts. But it is unclear to me how this interacts with the update(socket, :posts, fn posts -> [post | posts] end) function. I would expect that an updated post is then rendered at the top of the list. So, we would end up having 2 rendered versions of the post, one original one and one with the updates.

2 Likes

I think this works because of ids. Even though live view drops posts from memory but the client has posts in the dom. When a new post arrives it is id matched. If no such id exists it gets prepended at the top, otherwise it gets replaced.

1 Like

On related note to this older post, what exactly is the difference between

update(socket, :posts, fn posts -> [post | posts] end)

vs

assign(socket, :posts, [post|posts])

?

The first one uses an anonymous function to update the current value of the posts assign in the socket. The posts in the anonymous function is the current value of the posts assign in the socket.

The second call sets the posts assign to be [post|posts] , but this time posts doesn’t come from the socket 's assign, it’s a variable that has to be defined in the scope before the call, just like post.

It’s the same difference that exists between Map.put() and Map.update().

2 Likes