docs-forms.md
Forms in Hologram work seamlessly with the framework's declarative architecture. Hologram provides two flexible approaches for handling form inputs, allowing you to choose the best strategy for your specific use case.
Hologram supports two approaches for handling form inputs:
$change event handler on the input element level$change event handler on input element level. To get the values of such inputs, $change or $submit event handlers on a form element level can be usedSynchronized inputs maintain their displayed values in sync with your component's state through unidirectional data flow, ensuring the UI consistently reflects your application's data. This approach is similar to concepts like "controlled inputs" from React or "form input bindings" from Vue or Svelte, but maintains strict unidirectional flow - the component state is the single source of truth, and user input updates flow back to the state through event handlers.
To create a synchronized input, you need three things: state initialization, the input element synchronized with the state, and an event handler:
def init(_props, component, _server) do
put_state(component, :email, "")
end
<input type="email" name="email" value={@email} $change="email_changed" />
def action(:email_changed, params, component) do
put_state(component, :email, params.event.value)
end
Hologram synchronizes your application state with form inputs using different attributes depending on the input type:
value attribute (similar to React)checked attributeHologram optimizes this synchronization process:
Non-synchronized inputs don't have $change event handlers on the input element level. Instead, you can access their values using form-level event handlers. This approach is useful when you don't need real-time state synchronization and prefer to handle form data as a whole.
With non-synchronized inputs, you can access form data from params.event in both $change and $submit event handlers on the form level. The form data is provided as a map where keys are the input names and values are the current input values.
<form $change="form_changed" $submit="form_submitted">
<input type="text" name="username" />
<input type="email" name="email" />
<div class="error">{@validation_error}</div>
<button type="submit">Submit</button>
</form>
def action(:form_changed, params, component) do
# params.event contains all form data: %{username: "...", email: "..."}
# Validate the form whenever any field changes
validation_error = cond do
params.event.username == "" -> "Username is required"
params.event.email == "" -> "Email is required"
true -> ""
end
put_state(component, :validation_error, validation_error)
end
def action(:form_submitted, params, component) do
# params.event contains all form data: %{username: "...", email: "..."}
username = params.event.username
email = params.event.email
# Process form submission...
component
end
Note: Form-level $change and $submit event handlers can also be used with synchronized inputs. This allows you to handle both individual input changes (via input-level $change handlers) and form-wide operations (via form-level handlers) in the same form.
Hologram uses synthetic events similar to React. The $change event behavior depends on both the input type and where you place the event handler:
$change EventsWhen you place $change directly on form inputs:
$change maps to the native input event, triggering on every keystroke$change uses the native change event, triggering when the selection changesFor input-level $change events, the event data contains a value field with the specific element's value.
$change EventsWhen you place $change on the form element itself, it behaves like the native change event - typically triggering when a field loses focus, regardless of the input type. This is useful for validation workflows where you want to validate after the user finishes editing a field.
The event data contains all form field values as a map, where keys are the input names and values are the current input values.
Regardless of whether you use input-level or form-level events, the values have different types depending on the input type:
Hologram supports all standard HTML form elements, which can be used with either synchronized or non-synchronized approaches. Below are examples showing both approaches for each input type.
Text-based inputs (text, email, password) are the most common form elements. They use the value attribute for state synchronization:
Synchronized:
<input type="text" name="username" value={@username} $change="username_changed" />
<input type="email" name="email" value={@email} $change="email_changed" />
<input type="password" name="password" value={@password} $change="password_changed" />
Non-synchronized:
<input type="text" name="username" />
<input type="email" name="email" />
<input type="password" name="password" />
Textareas provide multi-line text input. They use the value attribute for state synchronization:
Synchronized:
<textarea name="content" value={@content} $change="content_changed" />
Non-synchronized:
<textarea name="content"></textarea>
Checkboxes allow users to select or deselect options. They use the checked attribute for state synchronization:
Synchronized:
<input type="checkbox" name="agreed" checked={@agreed} $change="agreement_changed" />
<label for="agreed">I agree to the terms</label>
Non-synchronized:
<input type="checkbox" name="agreed" value="true" />
<label for="agreed">I agree to the terms</label>
Radio buttons allow users to select one option from a group. They use the checked attribute for state synchronization:
Synchronized:
<input type="radio" name="size" id="size-small" value="small" checked={@size == "small"} $change="size_changed" />
<label for="size-small">Small</label>
<input type="radio" name="size" id="size-large" value="large" checked={@size == "large"} $change="size_changed" />
<label for="size-large">Large</label>
Non-synchronized:
<input type="radio" name="size" id="size-small" value="small" />
<label for="size-small">Small</label>
<input type="radio" name="size" id="size-large" value="large" />
<label for="size-large">Large</label>
Select elements provide dropdown menus. They use the value attribute for state synchronization:
Synchronized:
<select name="country" value={@country} $change="country_changed">
<option value="br">Brazil</option>
<option value="pl">Poland</option>
<option value="us">United States</option>
</select>
Non-synchronized:
<select name="country">
<option value="br">Brazil</option>
<option value="pl">Poland</option>
<option value="us">United States</option>
</select>
One of Hologram's key architectural advantages is that it runs Elixir in the browser, enabling you to perform validation client-side using the same code you'd use server-side. This means you can provide immediate feedback to users without network round-trips.
Your validation logic can be truly isomorphic - the same Elixir validation code (including Ecto changesets) runs both client-side and server-side:
For optimal user experience, consider this validation approach:
$change events to trigger validation when fields lose focusThis pattern provides the best of both worlds: immediate user feedback through client-side validation, with the security and reliability of server-side validation.
When working with forms in Hologram:
name attributes that match your data structure for easier form processing$change events for validation workflows that trigger when fields lose focusemail, password, etc.) for better UX and built-in validationSponsored by
Previous
Next