DynamicForm.RendererLive (DynamicForm v0.18.1)

Copy Markdown View Source

A LiveComponent version of the DynamicForm renderer with automatic state management.

This component handles form state, validation, and submission automatically, communicating with the parent LiveView via message passing.

Attributes

Required

  • :id - Component ID (string, required by LiveComponent)
  • :instance - DynamicForm.Instance struct, JSON string, or map containing form configuration

Optional

  • :on_change - 1-arity function (payload) -> payload run after the built-in validations on every change and during the submit validation pass (default: nil; see "Lifecycle callbacks" below)
  • :on_submit - 1-arity function (payload) -> payload run on every submit — valid or not (default: nil; see "Lifecycle callbacks" below)
  • :on_success - 1-arity function (payload) run on every valid submission instead of the default {:dynamic_form, payload} message to the parent LiveView (default: nil; see "Messages" below)
  • :data - Initial form data for edit mode — existing record values; a payload's data round-trips directly (map, default: %{})
  • :form_name - Form namespace for submitted params (string, default: "dynamic_form")
  • :submit_text - Submit button text (string, default: "Submit", not required when hide_submit is true)
  • :hide_submit - Whether to hide the submit button (boolean, default: false)
  • :gettext - Gettext backend module for translations (atom, default: DynamicForm.Gettext)
  • :validation_summary - Display validation errors at top of form (string, nil, "simple", or "detailed", default: nil)
  • :components - Custom components module (e.g. the app's Phoenix-generated CoreComponents); functions it exports override the built-ins per function — see DynamicForm.Components (atom, default: nil, falling back to the :dynamic_form, :components config)
  • :custom_field_types - Custom field types map (type name => Ecto type), merged over the :dynamic_form, :custom_field_types config — see DynamicForm.FieldTypes (map, default: nil)

Usage

Basic Usage

The component sends the parent LiveView a message on every valid submission — the parent performs the side effect:

<.live_component
  module={DynamicForm.RendererLive}
  id="contact-form"
  instance={@form_instance}
/>

def handle_info({:dynamic_form, %DynamicForm.Payload{data: data}}, socket) do
  {:ok, contact} = MyApp.Contacts.create_contact(data)
  {:noreply, put_flash(socket, :info, "Created contact #{contact.id}")}
end

Custom success handling

Define on_success to replace the default message with your own behavior (a differently-shaped message, a PubSub broadcast, or nothing at all):

<.live_component
  module={DynamicForm.RendererLive}
  id="contact-form"
  instance={@form_instance}
  on_success={fn payload -> send(self(), {:contact_saved, payload.data}) end}
/>

Edit Mode

Pre-populate the form with existing data:

<.live_component
  module={DynamicForm.RendererLive}
  id="user-profile"
  instance={@form_instance}
  data={%{"name" => "John", "email" => "john@example.com"}}
  form_name="user_profile"
/>

Disabled Fields

Fields can be marked as disabled: true in the form instance configuration. Disabled fields are displayed but cannot be edited by the user.

Important: Disabled HTML fields are not submitted by browsers, so their values are automatically preserved by merging the initial :data with form submissions. This ensures disabled field values remain in the changeset throughout validation and submission.

External Submit Button

You can place a submit button outside the form element by using the hide_submit option and DynamicForm.RendererLive.submit_button/1:

<DynamicForm.RendererLive.submit_button form="my-form-form">
  Save Changes
</DynamicForm.RendererLive.submit_button>

<.live_component
  module={DynamicForm.RendererLive}
  id="my-form"
  instance={@form_instance}
  hide_submit={true}
/>

Note: The form ID is automatically generated as "#{id}-form", so if your component ID is "my-form", the form element ID will be "my-form-form".

Lifecycle callbacks: on_change and on_submit

Both hooks mirror the form's phx-change/phx-submit events. Each is a 1-arity function that receives a DynamicForm.Payload — carrying the changeset after the built-in validations and the applied data — and returns the payload, transformed or untouched. Callbacks are for validation, not side effects: perform actions (database writes, navigation) in the parent's handle_info/2 instead.

on_change extends validation: it runs after the built-in validations on every change (and during the submit validation pass). Errors added via DynamicForm.Payload.add_error/4 render inline live, exactly like built-in validations. Keep it cheap — it runs per keystroke.

on_submit runs on every submit — valid or not — so it can batch expensive checks (API calls, database lookups) with the built-in errors into one complete error list:

<.live_component
  module={DynamicForm.RendererLive}
  id="contact-form"
  instance={@form_instance}
  on_submit={&MyApp.Contacts.verify/1}
/>

def verify(payload) do
  case verify_phone_number(payload.data[:phone]) do   # expensive, submit-only
    {:ok, normalized} ->
      DynamicForm.Payload.put_extra(payload, :normalized_phone, normalized)

    :error ->
      DynamicForm.Payload.add_error(payload, :phone, "is not a valid phone number")
  end
end

Messages

By default, the component sends the parent LiveView a message on every valid submission:

{:dynamic_form, %DynamicForm.Payload{}}

Invalid submissions never message the parent — their errors render inline on the form. The payload delivered is the one returned by the callbacks (or built by the component when none are configured), so anything stashed in :extra is available in handle_info/2.

Defining on_success replaces the default message: the function is called with the payload on every valid submission and no {:dynamic_form, payload} message is sent. Its return value is ignored. Use it to send a custom message, broadcast over PubSub, or make the form fully self-contained.

Summary

Functions

Renders a submit button that can be placed outside a form element.

Functions

submit_button(assigns)

Renders a submit button that can be placed outside a form element.

Uses the HTML form attribute to associate the button with a form by its ID. This allows the submit button to be placed anywhere on the page, not just inside the form element.

When using with DynamicForm.RendererLive, the form ID is automatically generated as "#{component_id}-form". For example, if your LiveComponent has id="my-form", the form element ID will be "my-form-form".

Examples

# LiveComponent with external submit button
<DynamicForm.RendererLive.submit_button form="contact-form-form">
  Submit Contact Form
</DynamicForm.RendererLive.submit_button>

<.live_component
  module={DynamicForm.RendererLive}
  id="contact-form"
  instance={@form_instance}
  hide_submit={true}
/>

# In a modal footer
<.modal id="edit-modal">
  <.live_component
    module={DynamicForm.RendererLive}
    id="user-profile"
    instance={@form_instance}
    hide_submit={true}
  />
  <:actions>
    <DynamicForm.RendererLive.submit_button form="user-profile-form">
      Save Profile
    </DynamicForm.RendererLive.submit_button>
  </:actions>
</.modal>

Attributes

  • form - The ID of the form element to submit (required)
  • class - Additional CSS classes to apply to the button
  • disabled - Whether the button is disabled

Attributes

  • form (:string) (required) - The ID of the form element to submit.
  • class (:string) - Additional CSS classes. Defaults to nil.
  • disabled (:boolean) - Whether the button is disabled. Defaults to false.
  • Global attributes are accepted. Supports all globals plus: ["name", "value"].

Slots

  • inner_block (required)