LiveVue
1.0
Simple Form
Build forms with useLiveForm()
that sync with Ecto changesets. Server-side validation with real-time feedback,
all powered by Phoenix LiveView.
What this example shows
How it works
1 Initialize the form with useLiveForm()
The useLiveForm()
composable takes the form prop and configuration. It returns reactive form state
and field accessors.
const form = useLiveForm(() => props.form, {
changeEvent: "validate",
submitEvent: "submit",
debounceInMiliseconds: 300
})
2 Access fields with form.field()
Each field provides reactive state including value,
errors
(array), errorMessage
(first error shortcut), isTouched,
and inputAttrs
for binding.
const nameField = form.field("name")
// errorMessage.value is equivalent to errors.value[0]
// Use: nameField.inputAttrs.value, nameField.errorMessage.value
3 Error display strategies
You can display validation errors in different ways depending on UX needs.
This example shows errors only after the user has interacted with a field
(isTouched),
which prevents showing errors before the user has had a chance to fill in the form.
<!-- Show error only after user interaction -->
<div v-if="nameField.isTouched.value && nameField.errorMessage.value">
{{ nameField.errorMessage.value }}
</div>
<!-- Always show error (useful after submit) -->
<div v-if="nameField.errorMessage.value">...</div>
<!-- Show all errors for a field -->
<div v-for="error in nameField.errors.value">{{ error }}</div>
4 Server validates with Ecto changeset
The LiveView uses
Ecto.Changeset
for validation. Use
to_form()
to convert the changeset to a form that LiveVue understands.
changeset = changeset(params) |> Map.put(:action, :validate)
assign(socket, form: to_form(changeset, as: :contact))
5 Bind inputs with v-bind and inputAttrs
Use v-bind
with inputAttrs.value
to automatically wire up value, name, id, and event handlers.
<input v-bind="nameField.inputAttrs.value" type="text" />
6 Submit and reset the form
Call form.submit()
to send the submitEvent
to the server, and
form.reset()
to restore initial values and clear touched state.
<button @click="form.submit()" :disabled="!form.isValid.value">
Submit
</button>
<button @click="form.reset()">Reset</button>
7 Reset form state after successful submit
When resetting the form after a successful submission, return
{:reply, %{reset: true}, socket}
instead of {:noreply, socket}.
This tells useLiveForm()
to clear the client-side touched state, preventing validation errors from showing on the fresh form.
def handle_event("submit", params, socket) do
%{"contact" => form_params} = params
changeset = changeset(form_params)
|> Map.put(:action, :insert)
if changeset.valid? do
# Reply with reset: true to clear touched state
new_form = to_form(
changeset(%{}),
as: :contact
)
{:reply, %{reset: true},
assign(socket, form: new_form)}
else
{:noreply,
assign(socket,
form: to_form(changeset, as: :contact))}
end
end