Skip to content

Advanced form validation in Magewire

Hyvä includes a JavaScript form validation library that can be used with Magewire form components, so that validation failure messages are automatically displayed next to the input elements in an accessible manner.

A magewire validation rule is automatically added in the Hyvä Checkout.

Preparing the form element

The form validation needs to be initialized on the form element as described in the form validation documentation.

Additionally, a novalidate attribute has to be added.

<form x-data="hyva.formValidation($el)" novalidate>
Under the hood: why is novalidate needed?

The form validation library adds the novalidate attribute to the form automatically when the form Alpine component is initialized.
However, when Magewire renders the form element again during subsequent requests, it will remove the novalidate attribute if it isn't also rendered in the template.

The effect of this is that the form validation works correctly during the first form submission, but during form submissions after subsequent requests the browser will apply native HTML5 form validation itself again without delegating to the JavaScript validation library.

Preparing the input elements

To add the validation rule to an input, add "magewire": true to the list of validations in the data-validate attribute JSON

data-validate='{"magewire": true}'

The current state of the form component attribute is specified with a data-magewire-is-valid data attribute.

data-magewire-is-valid="<?= (int) !$magewire->hasError('myInput') ?>"

valid == 1, invalid == 0

The attribute state has to be specified as an integer value of 1 if it is valid, or 0 if the component attribute is invalid.
Using a boolean true or false or other truthy/falsy values will not work.

Finally, if the form component attribute is invalid, the validation message has to be specified using the data-msg-magewire attribute.

<?php if ($magewire->hasError('myInput')): ?>
   data-msg-magewire="<?= $escaper->escapeHtmlAttr($magewire->getError('myInput')) ?>"
<?php endif; ?>

Putting it all together:

<input id="my-input"
       type="text"
       name="my-input"
       wire:model.defer="myInput"
       data-validate='{"magewire": true}'
       data-magewire-is-valid="<?= (int) !$magewire->hasError('myInput') ?>"
   <?php if ($magewire->hasError('myInput')): ?>
       data-msg-magewire="<?= $escaper->escapeHtmlAttr($magewire->getError('myInput')) ?>"
   <?php endif; ?>
>

Triggering form validation

Form validation is automatically triggered after Magewire updates an element during subsequent requests.

To trigger the validation on the frontend without a Magewire roundtrip, call the Alpine method validate() to validate all fields, or use @change="onChange" on a form element.

Submitting a form

If the form is bound to a Magewire method with wire:submit.prevent="submitForm" this will conflict with the JavaScript form validation.

To work around the issue, trigger the validate() method and call the Magewire form component target method if there are no errors.

<button type="button" class="btn btn-primary"
        @click="validate().then(() => $wire.submitForm()).catch(() => {})">
    <?= $escaper->escapeHtml(__('Save')) ?>
</button>
Under the hood: why does it conflict?

The Magewire wire:submit.prevent="" action adds a readonly attribute to all form elements before the subsequent request is made.
This happens before the Alpine form validation is processed.
The problem is that the readonly attribute prevents form elements from being validated, so the form will be submitted without any frontend validation.