Best practices for nested forms in LiveView?

I’m trying to figure out the correct pattern for large multi-step forms in LiveView, specifically those that contain collections and nested items. I looked over the 0.19 announcement, the Fly post (Dynamic forms with LiveView Streams · Fly) about dynamic streams, and the TodoTrek demo (GitHub - chrismccord/todo_trek).

It seems they take advantage of the fact that there’s a separate form per each TODO entry, which works well in this context, but you can’t nest a form in HTML, so I’m a bit lost as to how to correctly implement this type of nested collection in the context of an existing form.

For example, I have a item creation page. Here’s a super simplified version of the form:

Title
Description (this is a rich text editor with various inputs inside of it which ultimately result in HTML output)

Photos

List of "linked items"
  Create a linked item (create it in the DB and associate it with the pending item)
  Add an existing linked item
  Expand to show linked item
  Edit linked item
  Delete linked item from parent item (the one being created)

Imagine the “linked item” section being its own little interface with the ability to add/edit/link/delete, each of which would pop open a mini-form inside to do so.

Basically, the “linked item” component can be considered semantically similar to the kanban todo list in the TodoTrek demo app. But in this case, it’s part of the larger item creation form.

HTML doesn’t allow nesting forms and LiveView doesn’t support it either - if you try and nest a form, all the events of the inputs inside of the child form will just go straight to the parent form, the child form essentially gets ignored.

So I see a few options:

  1. Each of the inputs in the “linked items” component within the form has a manual phx-change and phx-target=@myself set, essentially bypassing the parent form and doing its own thing. Cumbersome, loses some of the form conveniences of LiveView, but doable.
  2. Break the big form down into small forms. Essentially,
form 1:
Title
Description

form 2:
Photos

"Linked items" is a stream (outside of the form concept entirely)
  mini-form for each item in the "linked items", like in TodoTrek

This would work but validating/submitting/collecting all the individual forms into a single push seems potentially quite complex.

  1. The entire page is a single form, with a single phx-change and the “linked items” is implemented using an embedded association. I’m not fully clear on if this solves the problem of allowing creating/linking/editing/deleting/rearranging in-line in the way that is shown in the TodoTrek demo, but perhaps it does.

  2. The “linked items” management happens in a modal, which takes it out of the flow of the parent <form> and enables the streams-with-subforms technique to work. This would work, but I don’t like it UI wise - I’d strongly prefer the “linked items” management to happen inline and not in a modal.

Any thoughts on the best route to go here?

1 Like

Long story short, you can accomplish what you want with Phoenix.Component — Phoenix LiveView v0.19.0 or Phoenix.HTML.Form — Phoenix.HTML v3.3.1 and Ecto.Changeset — Ecto v3.10.1.

2 Likes

I want to add a “wizard” style form example (so multi-step) form to the TodoTrek application, but I haven’t been able to get to it yet. We may or may not end up with primitives to help with this, but the gist of what you want and what any light LV feature would provide is rendering the entire form state across the steps in the form, but only show the inputs for the current step that matter. This ensures you get proper state recovery, so if you are only showing a subset of the fields at any given time, you are probably going to lose user state on occasion, which you don’t want, unless you’re persisting every step. My first pass once I get to this is a small function component that toggles a slot based on the current step, while rendering the other hidden.

16 Likes