Splitting a large Laravel Livewire Component

Published: Dec 1, 2020 by C.S. Rhymes

Sometimes, no matter what solution you use to build your system, there comes a point where everything tends to grow a bit big and edges towards being unmanageable. Luckily Livewire offers a solution to this problem.

What is Livewire?

Laravel Livewire offers a way to build dynamic interfaces, but instead of using a JavaScript library such as Vue or React, it allows developers to write their code in PHP components and Blade template files. It’s fantastic. If you haven’t used it before then I suggest you check it out.

What is the problem?

Lots of tutorials and examples tell you how to build something small, like a contact form or a todo list. This is absolutely fine as it allows the example to show the build from start to finish, but sometimes you get given a specification for a very large and complex system and are asked to build it.

Let’s use the example of a holiday car rental site. When you want to make your booking you need to store details of the customer, such as their name and address, the type of car you want to hire, as well as any extras such as additional insurance or even hotels for your stay.

One way of approaching this would be to create a single, large component with all the logic in and then a single view file that just keeps on getting longer and longer. It will probably work just fine, but there are some tools available that let you split this up into more manageable chunks.

Splitting the Component Class with Traits

Livewire v2 comes with a great feature of PHP called traits. Traits allow you to create a file that can be used within a class, giving the class the methods and properties of the trait, but allowing you to reuse the same trait from within multiple classes. This allows us to split the logic up between several smaller, more manageable files and ‘use’ them in the main component.

If we go back to our holiday car rental, we might need a trait that contains our methods for getting vehicle data, another for getting location information, such as addresses, or another for storing the customer’s details.

But Livewire traits take things even further too.

Rather than just allowing us to define our own methods we can make use of a naming convention to hook into the Livewire lifecycle hooks too.

We have said we will create a trait that is concentrated on vehicle information, but how do we make that data available to the rest of the component? We could call a method from the mount method of the main component class, but we could also do this in the trait by using the mount method of the trait.

If our trait was called VehicleInformation, then we can create a mountVehicleInformation method in our trait where we set the vehicle data to a public variable and it is then available to the main component.

trait VehicleInformation
{
    public $vehicles;

    public function mountVehicleInformation()
    {
        $this->vehicles = $this->getVehiclesData();
    }

    public function getVehiclesData()
    {
        // Get the vehicles data from somewhere
    }
}

From the example above, we can make use of $this->vehicles from with the main component class and $vehicles from within the blade view.

Splitting validation rules

Livewire gives us a way of creating rules as an array, which are validated when $this->validate(); is called.

class HolidayRental extends Component
{
    use VehicleInformation;

    public $vehicleType;

    protected $rules = [
        'vehicleType' => 'required',
        'startDate' => 'required|date',
    ];

    public function submit()
    {
        $this->validate();
    }
}

This works really nicely, but if we have a very large component, we could end up with may validation rules being defined in the main component class.

To help with the pattern we were using with traits, we could also extract the validation rules to the traits. There doesn’t seem to be anything built in to Livewire traits for this right now, but one method I have discovered is to define separate arrays in each trait and merge them in the constructor for the main component.

trait VehicleInformation
{
    public $vehicles;

    protected $vehicleInformationRules = [
        'vehicleType' => 'required',
    ];

    public function mountVehicleInformation()
    {
        $this->vehicles = $this->getVehiclesData();
    }

    public function getVehiclesData()
    {
        // Get the vehicles data from somewhere
    }
}
class HolidayRental extends Component
{
    use VehicleInformation;

    public $vehicleType;

    protected $rules = [
        'startDate' => 'required|date',
    ];

    public function __construct()
    {
        // Merge the component rules with rules from our trait
        $this->rules = array_merge($this->rules, $this->vehicleInformationRules);

        parent::__construct();
    }

    public function submit()
    {
        $this->validate();
    }
}

This also makes our validation rules more reusable across different components if needed.

Splitting the views

The view for the component is defined in the render method of the component. This is a single blade file.

class HolidayRental extends Component
{
    ...
    public function render()
    {
        return view('livewire.holiday-rental');
    }
}

For some reason it didn’t seem immediately obvious to me that you can use standard blade @include() from within the main livewire.holiday-rental blade view to include partials or sections of the form. I think this was due to my previous experience using Vue components where you have to play around passing state or syncing state between parent and child components. I was over thinking things. Blade files are views and the state is managed in the class so you can include as many subviews as you need and it will render them together.

Reusable sub components

One last thing to consider is how you can use the @include() tag to pass additional settings to a subview. In our example for holiday rentals we may need a search for hotels or car parks. We could build a single search results view that has a search box and displays an image, a title and a description for each result, but we could set what wire model the search box needs and which results to show by setting this in the @include() tag.

// livewire/holiday-rental.blade.php

<h2>Find a Hotel</h2>

@include('livewire.partials.search', ['searchModel' => 'hotelSearch', 'results' => $hotels])

<h2>Find a Car Park</h2>

@include('livewire.partials.search', ['searchModel' => 'carParkSearch', 'results' => $carParks])

// livewire/partials/search.blade.php

<input wire:model="{{ $searchModel }}>

@foreach($results as $result)
<div>
    <img src="{{ $result->image_path }}" alt="{{ $result->title }}" />
    <h3>{{ $result->title }}</h3>
    <p>{{ $result->description }}</p>
</div>
@endforeach

Planning it all out

Taking sometime to plan out and structure your project before you start diving into the code can bring benefits later on, reducing repetition and making your code more reusable and easier to maintain. I was lucky that I had some time available to spend a few days building a prototype to try out the above techniques before I started building my project for real. Hopefully it will pay off in the long run.

If there are any other techniques that you know of then please share them in the comments and hopefully you will enjoy working with Livewire as much as I do.

Photo by Sasha • Instagram.com/sanfrancisco from StockSnap

laravel livewire php

Share

Latest Posts

Refactoring to Laravel 8 Class Model Factories
Refactoring to Laravel 8 Class Model Factories

Laravel 8 introduced new class based model factories and if you have an existing project you can use the legacy factories package to keep using the old factories. I have tended to keep the factories as they were and continue development, but after working on a fresh Laravel 8 project and using the new class based syntax I decided to go back and update the factories in the older Laravel apps. This article explains how I went about refactoring the factories to classes.

The PHP array
The PHP array

Arrays are a useful toolt to store multiple values. You can access a specific value in an array using a key. By default, if you don’t specify keys, they will be numeric and start at zero (not one);

Run Laravel artisan commands in PHP Storm
Run Laravel artisan commands in PHP Storm

PHP Storm has a built in terminal window which you can use to run Laravel artisan commands, but there is a quick configuration change that you can do to make it even easier to run artisan commands.

How NOT to make a website

How NOT to make a Website

By C.S. Rhymes

From £8.99

Nigel's Intranet Adventure

Nigel's Intranet Adventure

By C.S. Rhymes

From £2.69