I’m trying to get my head around Phoenix.LiveView.
I have a Git diff table where I want to display reviews, a review can be created on each diff line.
A review is basically a comment thread. Basically a feature copy of GitHub’s commit reviews…
My .leex
template looks as follow:
<%= for delta <- @deltas do %>
<div class="columns">
<table class="blob-table diff-table" >
<tbody>
<%= for {hunk, hunk_index} <- Enum.with_index(delta.hunks) do %>
<tr class="hunk">
<td class="line-no" colspan="2"></td>
<td class="code" colspan="2">
<div class="code-inner nohighlight"><%= hunk.header %></div>
</td>
</tr>
<%= for {line, line_index} <- Enum.with_index(hunk.lines) do %>
<%= cond do %>
<% line.origin == "+" -> %>
<tr class="diff-addition">
<% line.origin == "-" -> %>
<tr class="diff-deletion">
<% true -> %>
<tr>
<% end %>
<td class="line-no"><%= if line.old_line_no != -1, do: line.old_line_no %></td>
<td class="line-no"><%= if line.new_line_no != -1, do: line.new_line_no %></td>
<td class="code origin">
<button class="button is-link is-small" phx-click="new-line-comment" phx-value="<%= oid_fmt(delta.new_file.oid) %>:<%= hunk_index %>:<%= line_index %>">
<span class="icon"><i class="fa fa-comment-alt"></i></span>
</button>
<%= line.origin %>
</td>
<% highlight_lang = highlight_language_from_path(delta.new_file.path) %>
<td class="code">
<div class="code-inner hljs <%= highlight_lang %>"><%= line.content %></div>
</td>
</tr>
<%= cond do %>
<% line_review = Enum.find(@line_reviews, &(&1.blob_oid == delta.new_file.oid && &1.hunk == hunk_index && &1.line == line_index)) -> %>
<tr class="inline-comments">
<td colspan="4">
<%= live_render(@socket, GitGud.Web.CommentThreadView, session: %{line_review_id: line_review.id}, child_id: "CommitLineReview-#{line_review.id}") %>
<div class="comment-form">
<%= if @line_review_form == {line_review.blob_oid, line_review.hunk, line_review.line} do %>
<%= form_for GitGud.Comment.changeset(%GitGud.Comment{}), "#", [phx_change: "validate-comment", phx_submit: "create-line-comment"], fn f -> %>
<div class="field">
<div class="control">
<%= textarea f, :body, class: "textarea" %>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button" type="reset" phx-click="cancel-line-comment">Cancel</button>
</div>
<div class="control">
<button class="button is-success" type="submit">Add comment</button>
</div>
</div>
<% end %>
<% else %>
<div class="field">
<div class="control">
<input class="input" placeholder="Leave a comment" phx-focus="new-line-comment" phx-value="<%= Enum.join([oid_fmt(delta.new_file.oid), hunk_index, line_index], ":") %>" />
</div>
</div>
<% end %>
</div>
</td>
</tr>
<% @line_review_form == {delta.new_file.oid, hunk_index, line_index} -> %>
<tr class="inline-comments">
<td colspan="4">
<div class="comment-form">
<%= form_for GitGud.Comment.changeset(%GitGud.Comment{}), "#", [phx_change: "validate-comment", phx_submit: "create-line-comment"], fn f -> %>
<div class="field">
<div class="control">
<%= textarea f, :body, class: "textarea" %>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button" type="reset" phx-click="cancel-line-comment">Cancel</button>
</div>
<div class="control">
<button class="button is-success" type="submit">Add comment</button>
</div>
</div>
</div>
<% end %>
</div>
</td>
</tr>
<% true -> %>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
</div>
<% end %>
My LiveView socket has @deltas
and @line_reviews
assigned. On each diff line, I need to check if a review is available:
line_review = Enum.find(@line_reviews, &(&1.blob_oid == delta.new_file.oid && &1.hunk == hunk_index && &1.line == line_index))
This condition is evaluated in a cond
block within the template. If line_review
is not nil
, I use live_render/4
to render the comment-thread:
<%= live_render(@socket, GitGud.Web.CommentThreadView, session: %{line_review_id: line_review.id}, child_id: "CommitLineReview-#{line_review.id}") %>
The problem I’m facing is that each time the template is re-rendered, LiveView remount a new CommentThreadView
for each associated line-review. This happens each time the user interacts with my view even when it does not have any impact on @delta
(which basically never changes) or @line_reviews
assigns.
Is my approach wrong? Is it even possible to combine comprehensions, conditional evaluations and nested live views in a performant way? Are they any tools/tricks I can use to debug what LiveView is evaluating (static and dynamic parts, etc.)?