LiveView form recovery does not work if components are removed from and added to the DOM

I’ve got an interesting one for all you LiveView peeps out there. I’m not 100% sure of my analysis, but allow me to paint a picture before I get to my question.

The system I am building is essentially a workflow-specific Kanban board with task cards arranged in a grid.

The kanban board is a LiveView. Clicking a card uses live_patch to open it in a modal dialog as a LiveComponent. On the card is a form with a phx-change event used for both validation and auto-recovery purposes. This question is going to be about auto-recover.

On page load of the LiveView (Kanban board), Phoenix gives the LV an ID, e.g. phx-Fn1s7NJqKqkaDAGB. When the first LiveComponent (the modal) is rendered, it gets an id too: phx-Fn1s7NJqKqkaDAGB-1-0, and so does the nested LiveComponent (the card): phx-Fn1s7NJqKqkaDAGB-2-0. As you can see, each LiveComponent gets the parent LV ID and an incremented suffix.

To test data recovery, I have exposed the LiveView socket on the Window object as ls, and can use ls.disconnect() and ls.connect() in the browser’s console to simulate a network drop. When I enter form data in the nested component, disconnect, and reconnect, the form data is recovered and everything’s grand. This works, because the newly generated live components have the same IDs as their already existing client side counterparts, i.e. phx-Fn1s7NJqKqkaDAGB-1-0 & phx-Fn1s7NJqKqkaDAGB-2-0.

However, when I close the modal normally (live_patch back to the LiveView URL), destroying the live components, and click on another card (or the same card, doesn’t matter), the live components get new IDs; the modal gets phx-Fn1s7NJqKqkaDAGB-3.0 and the card gets phx-Fn1s7NJqKqkaDAGB-4.0. Repeating this generates IDs 5 and 6, then 7 and 8, and so on.

No problem really, except that now when I drop and restore the network, after the LiveView gets re-rendered all unsaved data is lost. This is because the component ID suffixes are regenerated from 1 after the mount (as before), but their client side counterparts are not suffixed 1-0 and 2-0 as they were the first time, they are instead numbered, for instance, 7-0 and 8-0. Consequently, these client-side components from before the disconnect get destroyed and auto-recovery is not attempted.

To restate, if a LV component is removed from the DOM and added again, form auto-recovery will not work. Or so it appears.

Now, I don’t think anyone—even me—would call this a bug, but I was wondering if anybody had any thoughts on this behavior or, ideally, any workarounds. I suppose I could write an Amnesia-backed phx-auto-recover event or some such, but that seems like overkill for a simple form. I’m willing to do it, but I am hoping for some additional insight first. Maybe a client-side solution would be better? Maybe my analysis is all wrong? Any thoughts?

I only use state-less components so I don’t have this problem. I guess my conservative attitude towards fancier tech pays off sometimes.

FWIW, I was able to work around this issue with a judicious sprinkling of phx-update="ignore" attributes on the relevant forms. I’m not sure I’m crazy about this solution, but then I’m not sure why I’m not sure either.

You could also send the form content/changeset back to the parent LV with send(self(), {:card_edit, data}), then pass it to the next component when it’s rendered. But if you got it working without any additional message passing, that sounds even better.