As your Livewire components grow, so does the list of public properties. You have likely seen (or written) components that look like this:
1class UpdateUserProfile extends Component
2{
3 public $username;
4 public $email;
5 public $notification_email;
6 public $notification_sms;
7 public $theme_preference;
8 // ... and 10 more
9}
This "Property Explosion" makes components hard to read and test. Grouping them into an associative array (public $state = []) solves the clutter but kills type safety and code completion.
The Solution: Custom Wireable Objects
Livewire provides a magical interface called Wireable. It allows you to use Custom Classes as public properties, teaching Livewire exactly how to serialize (save) and unserialize (load) them.
Let's refactor the settings above into a clean Data Transfer Object (DTO).
Step 1: Create the DTO
Create a plain PHP class and implement Livewire\Wireable.
1namespace App\Data;
2
3use Livewire\Wireable;
4
5class UserSettings implements Wireable
6{
7 public function __construct(
8 public string $theme = 'light',
9 public bool $notifications = true,
10 public string $language = 'en'
11 ) {}
12
13 public static function fromLivewire($value)
14 {
15 // Hydration: Livewire passes the array back, we recreate the Object
16 return new static(
17 $value['theme'],
18 $value['notifications'],
19 $value['language']
20 );
21 }
22
23 public function toLivewire()
24 {
25 // Dehydration: We tell Livewire what data to send to the browser
26 return [
27 'theme' => $this->theme,
28 'notifications' => $this->notifications,
29 'language' => $this->language,
30 ];
31 }
32}
Step 2: Use it in your Component
Now your component becomes incredibly clean. You can type-hint the property, ensuring you always have a valid object, not a random array.
1class UpdateUserProfile extends Component
2{
3 // Strong typing!
4 public UserSettings $settings;
5
6 public function mount()
7 {
8 // Initialize with defaults or from DB
9 $this->settings = new UserSettings();
10 }
11
12 public function save()
13 {
14 // $this->settings is guaranteed to be an instance of UserSettings
15 // No more $this->state['theme'] ?? 'default'
16 User::update(['options' => $this->settings->toLivewire()]);
17 }
18}
Step 3: Binding in Blade (The Magic)
This is where it gets impressive. Livewire allows you to bind directly to the object's properties using dot notation, and it updates the object deeply.
1<form wire:submit="save">
2
3
4 <x-bt-select
5 label="Theme"
6 wire:model="settings.theme"
7 :options="['light' => 'Light', 'dark' => 'Dark']"
8 />
9
10 <x-bt-toggle
11 label="Enable Notifications"
12 wire:model="settings.notifications"
13 />
14
15 <x-bt-button type="submit">Save Settings</x-bt-button>
16</form>
Why is this better?
- Encapsulation: You can add logic to your DTO (e.g.,
public function isDarkMode(): bool).
- Reusability: You can use the
UserSettings class in Controllers, Jobs, and other Components.
- Type Safety: Your IDE knows exactly what
$this->settings contains.
Stop passing raw arrays around. Give your data the structure it deserves with Wireable.