- Best Practices
- Folder Structure
- Javascript
- Block Structure
- Dynamic Components
- Templates
- Directives
- Component Types
- Magic Actions & Properties
- Properties
- Flash Messages
- Redirects
- Listeners & Emits
- Lifecycle Hooks
- Browser Events
- Prefetching
- Lazy Updating
- Keydown Modifiers
- Restricted Public Methods
- Pagination
- Query String
- Display Loading State
- Plugins
- Component Resolvers
- Reset
- Forms
- Use the Magewire naming conventions and structures.
- Use Hydrators to manipulate data before or after a method gets called.
- Contribute or create an issue when you found bugs or sucurity issues.
- Keep components small and clean.
- Use the
My/Module/Magewire/Component
folder when you introduce a new component type. - The
$magewire
component object is available in the template by default.
Folder | Description |
---|---|
/src | Root folder inside your module |
/src/Magewire | All Magewire components live here (subfolder allowed) |
/src/view/frontend/templates/magewire | All Magewire component template files live here (subfolder allowed) |
The Magewire
(alias for Livewire
) object is globally available on the page. More information can be found
inside the Livewire docs.
- magewire:load
- magewire:update
- magewire:available
- magewire:loader:start
(event) => {}
- magewire:loader:stop
(event) => {}
<block name="magewire.explanation">
<arguments>
<argument name="magewire" xsi:type="object">\My\Module\Magewire\Explanation</argument>
</arguments>
</block>
<!--OR (with additional data)-->
<block name="magewire.explanation">
<arguments>
<argument name="magewire" xsi:type="array">
<item name="type" xsi:type="object">\My\Module\Magewire\Explanation</item>
</argument>
</arguments>
</block>
<!--OR (with a custom template path)-->
<block name="magewire.explanation" template="My_Module::magewire/my/child/explanation.phtml">
<arguments>
<argument name="magewire" xsi:type="object">\My\Module\Magewire\Explanation</argument>
</arguments>
</block>
Note: Template will automatically be set if a Magewire component has been set. When your component is named Foo, just create a foo.phtml inside the /view/templates/magewire folder.
There are cases where you want to use the full power of Magewire, but the block is not configured in a layout XML.
$this->layout->createBlock(Template::class)->toHtml()
To achieve this you have to add the same configuration as you do in a layout XML.
use Magento\Framework\View\Element\Template;
use \Magento\Framework\App\ObjectManager;
$this->layout->createBlock(Template::class)
// Setting a template is optional since Magewire can auto-bind the belonging template.
->setTemplate('My_Module::path/to/component/phtml.phtml')
// Bind a valid Magewire component onto the block so it can be recognized by the layout.
->setData('magewire', ObjectManager::getInstance()->create(\My\Module\Magewire\Explanation::class))
->toHtml()
Note: It's important to use create, otherwise when you try to use this Magewire component multiple they will share the data.
For widgets, the principle is the same. But here Magento does the rendering. So you cannot use setData()
to assign the
Magewire component. But we can set the component via the constructor
method.
class MyWidget extends Template implements BlockInterface
{
public function __construct(
Context $context,
array $data = []
) {
$data['magewire'] = ObjectManager::getInstance()->create(\My\Module\Magewire\Explanation::class);
parent::__construct($context, $data);
}
}
On the initial page request all information needed are here and we have no problem. But on subsequent request Magento doesn't find the layout information.
To solve this issue all information that are needed to create the block are transported in the fingerprint "dynamic_layout". The information contains the magewire component and all block data (widget configuration). With this information it is possible to dynamically create the missing block on a subsequent request.
Options within your block template. The $magewire
variable is by default available in your Component template.
<?php
/** @var Explanation $magewire */
use My\Module\Magewire\Explanation;
// Check if property exists or is not null.
$magewire->hasFoo();
// Get property value.
$magewire->getFoo();
// Call a custom method inside your component (if not set as uncallable).
$magewire->myCustomMethod();
// Overwrite a property after the (optional) mount method was executed.
$magewire->foo = 'barbar';
?>
Switch to another template during a subsequent request.
<div id="my-magewire-component">
<button type="button" wire:click="login">Login</button>
</div>
public function login()
{
$this->switchTemplate('My_Module::customer/account/dashboard.phtml');
}
Tip: Use the power of the layout xml to assign a "switch" template path as a data param assigned to the component. This way your component becomes more dynamic and extensible for other developers.
Ignore smart DOM diffing on specified elements within a Magewire component.
<!-- Thanks to the wire:ignore, this button won't be rerenderd when fresh HTML comes in. -->
<button onclick="this.innerText = Number(this.innerText) + 1" wire:ignore>0</button>
<!-- Wire model the foo value (your component needs a public $foo property). -->
<input type="text" wire:model="foo"/>
<!-- Magewire will return fresh HTML thanks to this echoing out the foo value. -->
<?= 'Foo: ' . $magewire->getFoo() ?>
This can be very powerful when you are in the situation where you want to keep the child blocks intact.
<div>
<!-- We assume property value of $foo is unequal to 'bar' on page load. -->
<button wire:click="$set('foo', 'bar')">Set Foo</button>
<!-- Fresh HTML will be injected after you've clicked the Set Foo button. -->
<span>
<?= 'Foo: ' . $magewire->getFoo() ?>
</span>
<!-- Wont change to its original state as long as the wire:ignore sits there. -->
<div wire:ignore>
<?= $block->getChildHtml('child.block.name') ?>
</div>
</div>
There are some specific cases where you have a select element including a wire:model
attribute without any
modifiers. In these cases, you want to save a selected option on change. This works fine for typical mobile or mouse
interactions. But it has a couple of issues when using a keyboard where you have multiple options starting with the same
couple of letters.
The wire:select
directive has two modifiers: debounce
and blur
. By default, debounce uses a
1500ms
delay, but you can change this by specifying a different delay in milliseconds, such as
debounce.2000ms
. blur syncs the model on element blur.
Modifiers
- Blur: Syncs model on element blur
- Debounce: Debounce on each keydown or select change
Important: To use wire:select, you must always defer the model to let wire:select take over, and you cannot use a value with the directive.
Here's an example of how to use wire:select
:
<!-- Only syncs on blur -->
<select wire:model.defer="country" wire:select.blur>
<!-- Syncs both on blur and on debounce -->
<select wire:model.defer="country" wire:select.debounce.blur>
<!-- Syncs both on blur and on debounce (waiting 3 seconds) -->
<select wire:model.defer="country" wire:select.debounce.3000ms.blur>
<option value="UA">Ukraine</option>
<option value="AE">United Arab Emirates</option>
<option value="GB">United Kingdom</option>
<option value="US">United States</option>
</select>
Using the wire:select directive improves the user experience by allowing them to select options with ease and continue typing without the options changing.
The base idea behind de default component is to keep things as simple and clean as possible without any constructor dependencies. Therefore I've decided to create multiple component to inherit from, who give you the option to use stuff like for instance property validation.
class Explanation extends Magewirephp\Magewire\Component {}
// OR
class Explanation extends \Magewirephp\Magewire\Component\Pagination {}
Note: More core component types will be added over time.
Toggle, set or emit without writing any PHP.
<!-- Toggle properties -->
<button wire:click="$toggle('foo')">Toggle Foo</button>
<!-- Set properties -->
<button wire:click="$set('foo', 'bar')">Set Foo</button>
<button wire:click="$set('foo', '$bar')">Set Foo with $bar property value</button>
<button wire:click="$set('foo', <?= $magewire->getBar() ?>)">Set Foo with $bar property value</button>
<!-- Emit to listeners -->
<button wire:click="$emit('someListener', [123, 'bar', true])"
<button wire:click="$emitTo('layout.block.name', 'someListener', [123, 'bar', true])"
<button wire:click="$emitSelf('someListener', [123, 'bar', true])"
<!-- Refresh -->
<button wire:click="$refresh()"
Change the behavior of magic methods for a single component with an overwrite.
public function set($key, $value) {}
public function toggle($key) {}
public function refresh() {}
Assign properties including a lifecycle updating
and updated
method.
public $foo = 'bar'
/**
* OPTIONAL METHOD: Gets executed right before the property gets assigned.
* You should in this case use a magic method called updatingFoo(string $value): string {}
*/
public function updating($value, string $name)
{
// Bad practice on listening for a name specific property (updatingFoo(string $value)).
if ($name === 'foo') { return ucfirst($value); }
// Best practice
return ucfirst($value);
}
/**
* OPTIONAL METHOD: Gets executed immediately after the property has been assigned.
* You should in this case use a magic method called updatedFoo(string $value): string {}
*/
public function updated($value, string $name)
{
// Bad practice on listening for a name specific property (updatedFoo(string $value)).
if ($name === 'foo') { return ucfirst($value); }
// Best practice
return ucfirst($value);
}
Note: Trap your public property value into a variable when you use for instance array functions, who accept variables as a reference, to avoid a lifecycle interruption.
Listen for updates on targeted properties.
public $foo;
/**
* OPTIONAL METHOD: Before the property gets updated.
* Gets executed before the updating() lifecycle hook.
*/
public function updatingFoo(string $value): string
{
// $value: foo
return ucfirst('updating-' . $value);
}
/**
* OPTIONAL METHOD: After the property has been updated.
* Gets executed after the updated() lifecycle hook.
*/
public function updatedFoo(string $value): string
{
// $value: set-updating-foo
return strtoupper('updated-' . $value);
}
Note: Final result of this property lifecycle would be "UPDATED-DEFINE-UPDATING-FOO"
Nested array properties can be targeted specifically.
class Explanation extends \Magewirephp\Magewire\Component
{
public $nested = ['foo' => ['bar' => 'Hello world']];
// Since v1.10.6: Before nested array properties are updating.
public function updatingNested(array $value): array
{
// Make sure "nested.foo.bar" is always strtolower (helloworld)
$value['foo']['bar'] = strtolower($value['foo']['bar']);
return $value;
}
// Before it's getting updated.
public function updatingNestedFooBar(string $value): string
{
// Returns ['foo' => ['bar' => 'HELLO WORLD']]
return strtoupper($value);
}
// After it has been updated.
public function updatedNestedFooBar(string $value): string
{
// Returns ['foo' => ['bar' => 'hello world']]
return strtolower($value);
}
// Since v1.10.6: After nested array properties are updated.
public function updatedNested(array $value): array
{
// Evantually strtoupper "nested.foo.bar" again (HELLOWORLD).
$value['foo']['bar'] = strtoupper($value['foo']['bar']);
return $value;
}
}
<!-- Input value will be 'Hello world' on initialization. -->
<!-- Input value will be synced onto the component on change. -->
<input wire:model="nested.foo.bar"/>
<!-- OR -->
<button wire:click="$set('nested.foo.bar', 'Hello outerspace')">Set</button>
Show a flash message on the page without a reload.
public function myCustomMessageMethod(string $message)
{
$this->dispatchErrorMessage($message);
$this->dispatchWarningMessage($message);
$this->dispatchNoticeMessage($message);
$this->dispatchSuccessMessage($message);
$this->dispatchMessage($messageType, $message);
}
Translations: Messages will automatically be transformed into a translatable phrase.
Message Types: When using the
dispatchMessage()
function, the first parameter must be one of the message types implemented byMagento\Framework\Message\MessageInterface
.
Redirect your customer with or without additional parameters.
public function myCustomRedirectMethod()
{
$this->redirect('/some/custom/path');
}
<button wire:click="myCustomRedirectMethod">Redirect</button>
<!-- OR -->
<div class="checkout-success-page" wire:poll.5000ms="myCustomRedirectMethod">
Thanks for your purchase! You will be redirected after 5 seconds.
</div>
Emit functionality in targeted or non-targeted components based on event listeners.
/**
* Component A.
* @block my.custom.block.name
*/
class A extends \Magewirephp\Magewire\Component
{
public function letsCallSomeone()
{
// Emit to every component who listens to 'youCanCallMe'.
$this->emit('youCanCallMe', ['value' => 'hi there']);
// Emit only to the 'my.custom.block.name' component.
$this->emitTo('my.custom.block.name', 'youCanCallMe', ['value' => 'hi there']);
}
}
/**
* Component B.
*/
class B extends \Magewirephp\Magewire\Component
{
protected $listeners = ['youCanCallMe'];
public function youCanCallMe($value) {}
// OR
protected $listeners = ['youCanCallMe' => 'toDoSomething'];
public function toDoSomething($value) {}
}
// Emit to every component who listens to 'youCanCallMe'.
Magewire.emit('youCanCallMe', {value: 'hi there'})
// Emit only to the 'my.custom.block.name' component.
Magewire.emitTo('my.custom.block.name', 'youCanCallMe', {value: 'hi there'})
You are able to use magic methods within your emits if this is required. Thanks to this feature you are able to for example refresh a component or set data without having to write this functionality inside your targeted component.
/**
* Component A.
* @block my.custom.block.name
*/
class A extends \Magewirephp\Magewire\Component
{
public function setSomeProperties()
{
// Force refreshes for a separate component who is listening.
$this->emit('$refresh', []);
// Set a public property value for a separate component who is listening.
$this->emitTo('layout.block.name', '$set', ['property' => 'someProperty', 'message' => 'hello world']);
// Toggle a property value for a separate component who is listening.
$this->emitTo('layout.block.name', '$toggle', ['value' => 'boolProperty']);
}
}
/**
* Component B.
*/
class B extends \Magewirephp\Magewire\Component
{
public $stringProperty;
public $boolProperty = false;
protected $listeners = ['$refresh']; // OR ['myEventName' => '$refresh']
// OR
protected $listeners = ['$set']; // OR ['myEventName' => '$set']
// OR
protected $listeners = ['$toggle']; // OR ['myEventName' => '$toggle']
}
Note: Emits only work during subsequent requests. They won't be dispatched on page load when you emit them in for example the
mount()
method. Usewire:init
to dispatch a method on page load where an emit could take place.
Each Magewire component has by default no $listeners
attached to itself. Still, you're able to refresh a
component from within another component thanks to a global refresh
listener who get's injected during a preceding
request.
Thanks to this global listener, Magewire introduces the emitToRefresh method. This gives the option to refresh any component on the page from within your own component.
public function refreshSomeOtherComponent()
{
$this->emitToRefresh('layout.block.name');
}
OR
<button wire:click="$emitTo('layout.block.name', 'refresh')"
Emits can also be caught as an Observer Event. By default, all emits are automatically transformed into an observable
event prefixed with magewire_
. This is deliberately done, so it doesn't accidentally interfere with existing
Magento events and also to make it more recognizable.
class A extends \Magewirephp\Magewire\Component
{
public function save()
{
$entity = $this->fooEntityFactory->create(['data' => ['foo' => 'bar']]);
$this->fooRepository->save($entity);
$this->emit('foo_entity_saved', ['entity' => $entity]);
}
}
Event foo_entity_saved
is now published as magewire_foo_entity_saved
.
<event name="magewire_foo_entity_saved">
<observer name="MyModuleMagewireFooEntitySaved"
instance="My\Module\Observer\Frontend\MyModuleMagewireFooEntitySaved"/>
</event>
class MyModuleMagewireFooEntitySaved implements \Magento\Framework\Event\ObserverInterface
{
public function execute(\Magento\Framework\Event\Observer $event): void
{
$entity = $event->getData('entity');
$this->session->setData('foo_entity_id' => $entity->getId());
// Check if the event was targeted to parent Magewire components only.
$event->getMetaData()->isAncestorsOnly(); // boolean
// Check if the event was targeted to itself only.
$event->getMetaData()->isSelfOnly(); // boolean
// Check if the event was targeted for a specific Magewire component.
$event->getMetaData()->isToComponent() // boolean
// Gets the layout block name of the targeted component.
$event->getMetaData()->getToComponent() // string (layout block name)
}
}
More info about Events and observers can be found here.
Each component undergoes a lifecycle. Lifecycle hooks allow you to run code at any part of the component's lifecyle, or before specific properties are updated.
// Called on all requests, immediately after the component is instantiated, but before any other lifecycle methods are called.
public function boot() {}
// Called on all requests, after the component is mounted or hydrated, but before any update methods are called
public function booted() {}
// Called when a Livewire component is newed up (think of it like a constructor)
public function mount(...$params) {}
// Called on subsequent Livewire requests after the component has been hydrated, but before any other action occurs
public function hydrate() {}
// Runs after a property called $foo is hydrated
public function hydrateFoo($value, $request) {}
// Runs before a property called $foo is updated
public function updatingFoo($value) {}
// Runs before updating a nested property bar on the $foo property
public function updatingFooBar($value) {}
// Runs before any update to the Livewire component's data (Using wire:model, not directly inside PHP)
public function updating($value, $name) {}
// Called after a property has been updated
public function updated($value, $name) {}
// Called after the "foo" property has been updated
public function updatedFoo($value) {}
// Called after the nested "bar" key on the "foo" property has been updated
public function updatedFooBar($value) {}
// Called after rendering, but before the component has been dehydrated and sent to the frontend
public function dehydrate() {}
// Runs before a property called $foo is dehydrated
public function dehydrateFoo($value, $response) {}
Notes:
- Be aware of the fact that a Magewire component state will get cached when for example FPC is enabled. This means the
mount()
method will only run once during an initial page load.- Lifecycle hooks that take a
$value
parameter must return a value - this is usually the$value
parameter itself.
You're able to trigger browser events from within your component.
public function openSubscribeModal()
{
// With data
$this->dispatchBrowserEvent('open-subscribe-modal', ['user' => $user->getFullName()]);
// Without data
$this->dispatchBrowserEvent('open-subscribe-modal');
}
<div x-data="{ open: false }" @open-subscribe-modal.window="open = true" x-show="open">
Thanks for your purchase! You will be redirected after 5 seconds.
</div>
<!-- OR -->
<div x-data="subscribeModal()" x-show="isOpen()">
Thanks for your purchase! You will be redirected after 5 seconds.
</div>
<script>
function subscribeModal() {
let self = this
// 'user' will exist inside event.detail like event.detail.user
window.addEventListener('open-subscribe-modal', event => {
self.show = !self.show
})
return {
show: false,
isOpen() { return self.show },
}
}
</script>
Prefetch a component and show differences on click.
public function prefetchMyContent()
{
$this->myContent('Hello world');
}
<button wire:click.prefetch="prefetchMyContent">Prefetch</button>
<?php if ($magewire->hasMyContent()): echo $magewire->getMyContent(); endif; ?>
Prevent sending out requests for every press of a button.
class Explanation extends \Magewirephp\Magewire\Component
{
public $lazyProperty;
}
<input type="text" wire:model.lazy="lazyProperty"/>
Perform actions on keydown.
public function keyUp()
{
$this->random(random_int(100, 999));
}
public function keyDown()
{
$this->random(random_int(10, 99));
}
<input type="text" wire:model="random" wire:keydown.arrow-up="keyUp" wire:keydown.arrow-down="keyDown"/>
You can also use vanilla JS instead of a PHP class method.
Quick List
- backspace
- escape
- shift
- tab
- arrow- right / left / up / down
Public methods can be restricted from subsequent request executions. Prevent method executions who are meant for inside the phtml template only. It's not a best practice and you should use a ViewModel in most cases.
<type name="Magewirephp\Magewire\Model\Action\CallMethod">
<arguments>
<argument name="uncallableMethods" xsi:type="array">
<item name="my_custom_set_method" xsi:type="string">myCustomSetMethod</item>
</argument>
</arguments>
</type>
File: etc/frontend/di.xml
class Explanation extends \Magewirephp\Magewire\Component
{
protected array $uncallables = ['myCustomSetMethod'];
}
Render a pagination pager inside your Component's view.
class Explanation extends \Magewirephp\Magewire\Component\Pagination
{
public $page = 1;
public $pageSize = 20;
}
<div id="my-magewire-component">
<?= $magewire->renderPagination() ?>
<!-- Switch the default template with a custom one -->
<?= $magewire->renderPagination('My_Module::html/pagination/custom_pager') ?>
</div>
Note: The query string feature is currently incomplete compared to the original Laravel Livewire implementation. At the moment you can only set public properties values via the URL when loading a page.
Define public properties via URL params on a page load
class MySearchForm extends \Magewirephp\Magewire\Component
{
public $searchText;
/**
* @url https://your.domain/customer/account/dashboard?searchText=foo
*/
protected $queryString = [
'searchText'
];
// OR
/**
* @url https://your.domain/customer/account/dashboard?q=foo
*/
protected $queryString = [
'searchText' => [
'alias' => 'q' // map 'searchText' as 'q'
]
];
}
Display a loading state only when performing a (targeted) subsequent method call.
class Explanation extends \Magewirephp\Magewire\Component
{
public $bar = null
// Show a loading state for specific methods.
protected $loader = ['foo'];
// Shows 'Updating foo' as a notification when 'foo' gets re-synced.
protected $loader = ['foo' => 'Updating foo'];
// Shows 'Loading...' as a notification when there is a interaction with the component.
protected $loader = 'Loading...';
// Prevent any loading activity displayment.
protected $loader = false;
// Prevent notification displayment and only show the main loader.
protected $loader = true;
// Show both messages for method execution and property syncing.
protected $loader = [
'{public_property}' => 'Updating something', // e.g. 'bar'
'{public_method}' => 'Executing something', // e.g. 'foo'
'{listener_event}' => 'Some event was executed, I\'m executing method foo' // e.g. 'some_event'
];
protected $listeners = ['some_event' => 'foo'];
public function foo() {
// Loading state will stay active until the listener has run
// Loading state will disapear when there are no active listeners
$this->emit('some_event');
}
public function bar() {
//
}
}
Note: Keep in mind that the
$loader
mapping only understands subsequent executable methods.
<!-- A loading bar will appear and disappear on load when method foo is mapped -->
<button wire:click="foo">Execute "foo"</button>
Since version: 1.10.0
<body>
<!-- Change the global loading indicator -->
<referenceBlock name="magewire.loader.overlay-spinner" template="My_Module::html/loader/custom-spinner.phtml"/>
<!-- Change the global loading overlay (please use the original as a reference) -->
<referenceBlock name="magewire.loader.overlay" template="My_Module::html/loader/custom-overlay.phtml"/>
<!-- Change the global notification messenger (please use the original as a reference) -->
<referenceBlock name="magewire.loader.notifications.messenger" template="My_Module::html/loader/notifications/custom-messenger.phtml"/>
<!-- Change (only) the notification messenger loading spinner -->
<referenceBlock name="magewire.loader.notifications.messenger.spinner" template="My_Module::html/loader/custom-message-spinner.phtml"/>
</body>
File: view/frontend/layout/default_hyva.xml
<script>
window.addEventListener('magewire:loader:start', () => {
document.body.style.cursor = 'wait'
})
window.addEventListener('magewire:loader:stop', () => {
document.body.style.cursor = 'pointer'
})
</script>
File: html/magewire/loader.phtml
In some cases you want to implement your own loader because you have a global one in place or your just don't need to notify your customer. Whatever the case, I centered all frontend logic into two phtml files to let you do whatever's needed for the project.
<body>
<!-- Remove only the indicator to still be able to hook into the available events -->
<referenceBlock name="magewire.loader" remove="true"/>
<!-- Remove all JavaScript beloning to the loader indicator -->
<referenceBlock name="magewire.plugin.loader" remove="true"/>
</body>
File: view/frontend/layout/default_hyva.xml
You're able to build it more custom for your wired component only.
<div x-data="{d: false}">
<button wire:click="start(5)" x-on:click="d = true" :disabled="d" x-on:switch-state.window="d = !d">
Start
</button>
</div>
public function start(int $seconds)
{
// Let's take a nap.
sleep($seconds);
// Unlock the disabled state.
$this->dispatchBrowserEvent('switch-state');
}
Note: This is just an example. For a disabled state you should or could use the
wire:loading
directive.
Each notification item can be one of three types (syncInput, fireEvent, callAction). By default, a notification item
has the magewire-notification
class. A related (kebab-cased) subclass will be bind dynamically based on the
notification type.
magewire-notification fire-event
magewire-notification sync-input
magewire-notification call-method
Plugins are a good and easy way to create frontend functionality which can hook into initial page loads, subsequent request hooks.
To register a custom plugin, you can reference a specific container which will take care of rendering your custom code at the right place.
An example on how you should register a custom frontend plugin:
<referenceContainer name="magewire.plugin.scripts">
<block name="magewire.plugin.my-custom-plugin"
template="My_Module::page/js/magewire/plugin/my-custom-plugin.phtml"/>
</referenceContainer>
<block name="magewire.plugin.loader"...
The Loader plugin is closely related to the $loader
property within a component. To disable the loader, you can
either access the system configuration at Store > Settings > Advanced > Developer > Magewire or remove the block
through layout XML.
Note: All Magewire specific settings by default can be found at Store > Settings > Advanced > Developer > Magewire.
- Loader / Show: Show or hide the global loading spinner.
- Loader / Enable Notifications: Show or hide optional notification messages.
- Loader / Notifications / Message Fadeout Timeout: Determine the duration for the message to fade out after its target component has fully loaded.
The loader is divided into several elements, giving you greater flexibility in customizing the appearance of both the global spinner and notifications, without having to overwrite everything.
All elements can be found in the Magewire core layout default_hyva.xml
or be found in Magewirephp_Magewire::html/loader
.
<block name="magewire.plugin.error"...
The Error plugin disables the native exception modal in Magento's Production mode and instead displays exceptions
in the dev-console. Customization of the message for each HTTP status code can be achieved using the layout XML by
searching for the status_messages
argument. This feature enables you to easily modify the HTTP status code
messages for specific pages according to your needs.
For handling page expiration, the Magewire.onPageExpired(callback)
method is used. By default, this method
throws an alert()
with a default message. Just like exceptions in production, this message can be overwritten.
Page expirations are represented by a 419 status code.
Magewire's default 419 behavior can be overridden, allowing you to modify it according to your requirements.
<referenceContainer name="magewire.plugin.scripts">
<!-- Make sure it's loaded after Magewire's default page expired handling. -->
<block name="magewire.plugin.on-page-expired"
after="magewire.plugin.error"
template="Example_Module::page/js/magewire/plugin/on-page-expired.phtml"
/>
</referenceContainer>
<script>
'use strict';
// Please be aware of the fact that this will overwrite the original callback.
Magewire.onPageExpired(() => {
// A new onPageExpired callback function is registered for Magewire. Therefore, this will
// be used when a page session expires. There is no return value required. You just need
// to make the use aware and in a way the page should be reloaded.
})
</script>
You can also overwrite or extend Magewire's onError callback.
<referenceContainer name="magewire.plugin.scripts">
<block name="my-custom.magewire.plugin.error"
after="magewire.plugin.error"
template="Example_Module::page/js/magewire/plugin/error.phtml"
/>
</referenceContainer>
<script>
'use strict';
(() => {
const magewireOriginOnErrorCallback = Magewire.components.onErrorCallback;
// Variable status is the HTTP response code (500, 404, 301 etc.)
Magewire.onError((status, response) => {
magewireOriginOnErrorCallback(status, response)
// Make sure to clone the response to avoid locking.
response.clone().text().then((result) => {
result = JSON.parse(result)
console.error(result.message || 'Something went wrong')
}).catch((exception) => {
console.error(exception)
})
})
})()
</script>
Since version: 1.9.0
The ResolverInterface enables you to implement your own method for constructing and reconstructing a component. This pattern is useful for examples like Dynamic Blocks and Widgets, which are not typically implemented using Layout XML.
With this pattern, you can inject custom ResolverInterfaces that implement a function called complies(). In this function, you can verify if a given Block belongs to your custom Resolver. If it does, the Resolver will grab a unique name from that Resolver and automatically bind it onto the request fingerprint.
This way, the right Resolver can be re-used when a component is reconstructed on a subsequent request. "Reconstruct" means that it uses all the ingredients to try and rebuild the component in the exact same way as it was constructed on page load.
The gateway into Magewire remains the same, requiring a Block "magewire" data key. As soon as Magewire finds the required data key, it passes the block. However, it does require some custom logic to ensure your block complies with this requirement.
If a block does have a magewire key, but none of the Resolvers comply, it will automatically fall back to the original Layout Resolver. This pattern is fully backwards compatible with older Magewire versions.
use \Magento\Widget\Block\BlockInterface as WidgetBlockInterface
class Widget implements ResolverInterface
{
public function getName() {
return 'widget';
}
/**
* Check if the given block is of instance type WidgetBlockInterface
*/
public function complies(BlockInterface $block): bool {
return $block instanceof WidgetBlockInterface;
}
public function construct(Template $block): Component {
// Load a widget, construct and return the Component.
}
public function reconstruct(RequestInterface $request): Component {
// Use what's available in the RequestInterface to reconstruct and return the component.
}
}
Important: When it complies, the resolver name will be cached inside the Magewire Resolver cache based on the Block cache key. This way, the Component Resolver doesnt have to verify each block over and over to check which Resolver complies to the given block. Therefor it's important to be aware of this caching layer.
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"
>
<!-- Register custom Component Resolver -->
<type name="Magewirephp\Magewire\Model\ComponentResolver">
<arguments>
<argument name="resolvers" xsi:type="array">
<item name="widget" xsi:type="object">Example\Module\Model\Magewire\Component\Resolver\Widget</item>
</argument>
</arguments>
</type>
</config>
File: etc/frontend/di.xml
Reset public property values to their initial state.
class Explanation extends \Magewirephp\Magewire\Component
{
public $foo;
public $bar = true;
public function boot(): void
{
$this->foo = 1337;
}
// Will reset all available pulbic properties.
public function resetAll()
{
$this->reset();
}
// Will only reset the 'foo' property.
public function resetFoo()
{
$this->reset(['foo']);
}
// Will only reset the 'foo' property and run the boot() method afterwards.
public function resetFooWithBoot()
{
$this->reset(['foo'], true);
}
}
Validate forms based on optional rules and messages.
class Explanation extends \Magewirephp\Magewire\Component\Form
{
public $foo;
// Always make sure the nested 'bar' property has a default value to avoid
// bar being seen as a value of key zero.
public $nesting = ['bar' => ''];
// Determine the rules for your properties (optional).
protected $rules = [
'foo' => 'required|min:2',
'nesting.bar' => 'required|min:2|max:6',
];
// Overwrite default rule messages or define a global for each property (optional).
protected $messages = [
'foo:min' => 'He! the minimal input length of :attribute needs to be 2 instead of :value.',
'nesting.bar:required' => 'The "Nesting Bar" property can\'t be empty...',
'nesting.bar:max' => 'Take it easy, just six characters allowed.',
];
public function save()
{
// Will throw a ValidationException which extends from AcceptableException who won't break the lifecycle when
// it gets thrown. Still you can catch it and change course if you need to.
$this->validate();
$this->dispatchSuccessMessage('Validation succes');
}
}
Go and read the Rakit/Validation documentation for more information.
Use Magento's regular i18n translations to translate form validation messages.
":attribute value (:value) has a minimal length of two.",":attribute value (:value) has a minimal length of two."
Note: Both
:attribute
and:value
can be used when required.
By default, messages aren't shown on the page after a validation failure. You have to put in some work in order to let the user know what happend. This can be done in several ways.
Show corresponding error messages below the field.
<form>
<input type="text" wire:model="foo"/>
<?php if ($magewire->hasError('foo')): ?>
<span class="text-red-800">
<?= $magewire->getError('foo') ?>
</span>
<?php endif ?>
</form>
Display a stack of error messages on above the form.
<?php if ($magewire->hasErrors(): ?>
<ul>
<?php foreach ($magewire->getErrors() as $error): ?>
<li class="text-red-800"><?= $error ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<form>
<input type="text" wire:model="foo"/>
</form>
Within this example, we use a try-catch structure to catch optional validation failure. This isn't required by default where the lifecycle is able to handle these ValidationException's by default.
class Explanation extends \Magewirephp\Magewire\Component\Form
{
public $foo;
public $rules = [
'foo' => 'required|min:2',
];
public function save()
{
try {
$this->validate();
} catch (\Magewirephp\Magewire\Exception\ValidationException $exception) {
foreach ($this->getErrors() as $error) {
$this->dispatchErrorMessage($error);
}
}
}
}
Catch global validation exceptions.
class Explanation extends \Magewirephp\Magewire\Component\Form
{
public $foo;
public $rules = [
'foo' => 'required|min:2',
];
public function save()
{
try {
$this->validate();
} catch (\Magewirephp\Magewire\Exception\AcceptableException $exception) {
// When you want to render the error in the view (key can be changed if required).
$this->clearErrors()->error('validation_exception', $exception->getMessage());
// When you want to notify the customer with a flash message.
$this->dispatchErrorMessage($exception->getMessage());
}
}
}
<form>
<?php if ($magewire->hasError('validation_exception'): ?>
<p>Form exception thrown: <?= $magewire->getError('validation_exception') ?></p>
<?php endif ?>
<input type="text" wire:model="foo"/>
</form>