How to implement conditional phx-update (append or replace) with Phoenix LiveView?

Hi guys. First of all, LiveView is awesome. Thanks to everyone contributing.

I have a page with a filter (search and sort) and “load more” type pagination.

I’m using temporary_assigns for the collection of items and phx-update="append" for the pagination.

However, when filter form is changed, I want the collection items to be replaced instead of appended. When, for example, the sorting is changed, the collection of items should start from page 1.

So is it possible to alter the behaviour of phx-update conditionally? How can I utilize the functionalities of both append and replace in the same view/component?

I have tried hooking the form to empty the collection, but failed:

Hooks.Replace = {
    beforeUpdate() {
        const nodeEmptied = document.getElementById("to-be-replaced-elem");
        while (nodeEmptied.firstChild) {
            nodeEmptied.removeChild(nodeEmptied.firstChild);
        }
    }
}

Any help would be greatly appreciated.

1 Like

You don’t need hooks for this. Here’s my approach:

phx-update can take 4 possible values:

    replace - the default operation. Replaces the element with the contents
    ignore - ignores updates to the DOM regardless of new content changes
    append - append the new DOM contents instead of replacing
    prepend - prepend the new DOM contents instead of replacing

I created a new assign :update_action and I’m passing it like this to the template: phx-update="<%= @update_action %>"

So, you only need to switch between append or replace in the :update_action assign depending on your logic.

However, when filter form is changed, I want the collection items to be replaced instead of appended. When, for example, the sorting is changed, the collection of items should start from page 1.

Here, you would change :update_action to replace.

When you only load more without actually changing any filters, it would be in append state.

So, in initial mount, it’s set to append
Then, in handle_params it’s set to replace since this is where the logic happens when filtering
Then, in handle_event("load-more", _, _), I set it again to append

Hope this helps.

29 Likes

It works. Thanks for sharing your solution!

4 Likes

Thanks, this is super helpful. I haven’t seen this solution mentioned elsewhere.

Perhaps it should be in the LiveView docs?

1 Like

You saved my day!

This is a neat trick. Is this still a go-to solution?

Isn’t there a way to surgically remove just the one deleted element from the DOM in a proper live view way? That means no javascript trickery, no “hidden” css classes, etc.

I notice docs mention phx-remove, but I cannot find any details what it does and how it works.

Also other neat trick that Jose Valim mentioned here on forum is to just change parent DOM element’s id via assigns, which will trigger it’s re-render. That might be useful for other scenarios.

It still works fine for me.

With temporary assigns, I’m not aware of any technique to do it surgically as you put it. I’ve heard talk about “smart collections” coming to LiveView. I think that will remove any current friction.

There’s an example on the Phoenix.Liveview.JS docs page.

Would you remember who or where gave that talk, or how could I find it? I assume Chris McCord recently?

ElixirConf 2021 - Chris McCord - The Future of Full-stack (at 50:54 there’s a roadmap slide). The demo in the talk uses phx-remove and the new stuff, worth watching.

2 Likes

Hi, does anyone know what are the differences between prepend and append?

I couldn’t find any related article to talk about the differences. I am just curious.

For any new item, prepend will add it to the beginning of the DOM node, and append will add it to the end.

Ex.
Prepend:

<div phx-change="prepend">
  <span id="1">Item 1</span>
</div>
# Add Item 2
<div phx-change="prepend">
  <span id="2">Item 2</span>
  <span id="1">Item 1</span>
</div>

Append:

<div phx-change="append">
  <span id="1">Item 1</span>
</div>
# Add Item 2
<div phx-change="append">
  <span id="1">Item 1</span>
  <span id="2">Item 2</span>
</div>
1 Like