quinten-kamphuis

quinten-kamphuis

Hi,

I’m running into an issue with a form inside of my rich editor live component. For the editor I have a button that opens a modal with a link insertion form. The problem is that the editor itself is also rendered inside a form of course. This causes issues and is invalid html right?

First I tried not having a form in the modal but saving the fields values on change in the assigns of the editor component. But pressing enter would still submit the parent form. I experimented with javascript to prevent the enter event but this felt too hacky.

Then I tried moving or “portalling” the modal component to the so that it’s form is no longer part of the form the editor is part of. But I could not get this to work, live view could no longer perform any updates and I believe it lost track of the modal since it was no longer in the same place. I also tried teleport with Alpine.js but I had similar issues here.

Hoping anybody knows a proper solution to handling nested form structures. Or proper live view portalling that would solve this.

Thanks!

Showing Posts 1 to 8

gushonorato

gushonorato

This is not a Phoenix/LiveView issue; the HTML5 standard prohibits nesting <form> tags. My first choice to address this would be to find another rich text editor that doesn’t generate <form> tags inside modals. If that’s not an option, could you please provide more details about the rich text editor you’re using and the structure of the parent (LiveView) form?

quinten-kamphuis

quinten-kamphuis OP

Hi, Thank you for helping out!

I created the rich editor myself, the problem is not necessarily about the editor, I should’ve been clearer about that. The issue is that I have a modal with a form inside a live component and that live component lives in another form. I can’t really lift the modal portion out. That’s why I tried moving or portalling or teleporting the modal to the body element but this caused issues with live view updates.

I could be wrong, but I feel like this is a fundamental framework issue. Other frameworks and even Alpine.js support simple portal or teleport operations and with react this would be even easier. Not to bash on Phoenix, it’s best, I love it, but if you’re deeply nested in a component there should be a way to render stuff elsewhere to prevent collision or invalid HTML while live view keeps working correctly.

I recreated my situation in a very basic manner to outline the problem better:

defmodule MyAppWeb.EmailEditorLive do
  use MyAppWeb, :live_view
  
  def mount(_params, _session, socket) do
    changeset = %{email_subject: "", email_body: ""}
    
    {:ok, 
     socket
     |> assign(:form, to_form(changeset))}
  end

  def render(assigns) do
    ~H"""
    <div class="container mx-auto">
      <h1>Email Editor</h1>
      
      <.form for={@form} phx-submit="save">
        <div class="space-y-4">
          <div>
            <.input field={@form[:email_subject]} label="Subject" />
          </div>
          
          <.live_component
            module={MyAppWeb.RichEditorComponent}
            id="rich-editor"
            field={@form[:email_body]}
          />
          
          <div>
            <.button type="submit">Save Email</.button>
          </div>
        </div>
      </.form>
    </div>
    """
  end
  
  def handle_event("save", %{"email" => params}, socket) do
    # Handle the main form submission
    {:noreply, socket}
  end
end

defmodule MyAppWeb.RichEditorComponent do
  use MyAppWeb, :live_component
  
  def mount(socket) do
    {:ok, 
     socket
     |> assign(:show_link_modal, false)
     |> assign(:link_form, to_form(%{"url" => "", "text" => ""}))}
  end
  
  def render(assigns) do
    ~H"""
    <div>
      <div class="border p-2">
        <div class="flex gap-2">
          <button type="button" class="p-1 border rounded">Bold</button>
          <button type="button" class="p-1 border rounded">Italic</button>
          <button 
            type="button" 
            class="p-1 border rounded"
            phx-click="show_link_modal"
            phx-target={@myself}
          >
            Add Link
          </button>
        </div>
        
        <div class="mt-2">
          <textarea 
            name={@field.name}
            class="w-full h-40 border rounded"
            value={@field.value}
          ></textarea>
        </div>
      </div>

      <.modal :if={@show_link_modal} id="link-modal" show>
        <.form for={@link_form} phx-submit="save_link" phx-target={@myself}>
          <div class="space-y-4">
            <div>
              <.input field={@link_form[:text]} label="Link Text" />
            </div>
            <div>
              <.input field={@link_form[:url]} label="URL" />
            </div>
            <div class="flex justify-end gap-2">
              <.button 
                type="button"
                phx-click="cancel_link"
                phx-target={@myself}
              >
                Cancel
              </.button>
              <.button type="submit">
                Insert Link
              </.button>
            </div>
          </div>
        </.form>
      </.modal>
    </div>
    """
  end
  
  def handle_event("show_link_modal", _, socket) do
    {:noreply, assign(socket, :show_link_modal, true)}
  end
  
  def handle_event("cancel_link", _, socket) do
    {:noreply, assign(socket, :show_link_modal, false)}
  end
  
  def handle_event("save_link", %{"link" => params}, socket) do
    # Handle the link form submission
    {:noreply, 
     socket
     |> assign(:show_link_modal, false)}
  end
end
cmo

cmo

You can split the modal into its own component and put it after the other form in the HTML.

There is talk of portals in the liveview issue tracker, if you want to give your 2c.

quinten-kamphuis

quinten-kamphuis OP

Yes, agreed. Although I would need PubSub or a hook for the modal to talk to the editor. And it does not make sense for the parent component to have a link insertion modal in it, they belong in the editor since they’re tightly coupled.

This is basically the best option for now right? Portals would probably solve this so I’ll take a look at that issue. Thanks for providing your insights!

cmo

cmo

I don’t see why showing/hiding that modal or adding a link to the text should involve the backend. I would use a hook for the link adder.

You can look into using a self closing form tags for the outer form so that the editor form id not nested inside another.

quinten-kamphuis

quinten-kamphuis OP

It’s not a question of server vs client side right? In both cases we would end up with either a nested form or highly controlled input fields inside of the wrong form.

And totally don’t understand what you mean with

You can look into using a self closing form tags for the outer form so that the editor form id not nested inside another.

Forms don’t have self-closing tags according to my knowledge.

cmo

cmo

I was speaking a little off topic. Every time you send an event to the backend to change the visibility of an element, a latency fairy dies.

Sorry, empty form tags, not self closing ones.

quinten-kamphuis

quinten-kamphuis OP

Right, pretty clever! But it would make the editor no longer self-contained. Not the biggest issue since I only use it in one place, so this will solve my issue for now. It doesn’t solve the bigger problem though, we should just be able to have self contained components with forms in phoenix without them clashing with the form they’re rendered in, either with portals or something else, right?

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews