Forms

Hybrid Form

A LiveView form with a React control inside it

  • forms
  • Phoenix.HTML.Form encoder

Key concepts

A React component can sit inside an ordinary <.simple_form> alongside HEEx inputs, as long as it renders a field named the way Phoenix.HTML.Form expects. It doesn't need pushEvent at all — the field's value reaches phx-change the same way any native input's would.

hybrid_form_live.ex
defmodule MyAppWeb.HybridFormLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    form =
      to_form(%{"email" => "hello@mrdotb.com", "delay_between" => [4_000, 30_000]}, as: :settings)

    {:ok, assign(socket, form: form)}
  end

  def render(assigns) do
    ~H"""
    <.simple_form id="settings-form" for={@form} phx-change="validate" phx-submit="submit">
      <.input field={@form[:email]} label="Email" />
      <.react
        name="HybridForm"
        inputName="settings[delay_between]"
        value={@form[:delay_between].value}
        min={2_000}
        max={90_000}
        step={2_000}
        socket={@socket}
      />
      <:actions>
        <.button>Save</.button>
      </:actions>
    </.simple_form>
    """
  end

  def handle_event("validate", %{"settings" => settings}, socket) do
    form = to_form(settings, as: :settings, action: :validate)
    {:noreply, assign(socket, form: form)}
  end

  def handle_event("submit", _params, socket) do
    {:noreply, socket}
  end
end

How it works

The slider passes inputName="settings[delay_between]" straight through to Radix's name prop, which renders hidden inputs under that name for each thumb. The browser submits them like any other form field, so phx-change="validate" fires exactly as it would for a native <input>.

The component's own dragging state — the thumb positions mid-drag — lives in useState and never touches the server; only a real change event submits a value, same as a native range input.