DEV Community

KILLALLSKYWALKER
KILLALLSKYWALKER

Posted on

A New Hope for Job Cards: Clean Laravel Blade Components

When I first started building a job portal in Laravel, I thought Blade templates were just views with a sprinkle of @if and @foreach. I dont really know that Blade had a component system that could make my code cleaner, reusable, and much easier to maintain.

Back then i think only React or Vue can do things like this for reusable component .

So whenever i need to display different type of jobs ( Feature , Premium , Hot , Normal ) my blade become really messy .

<div class="border p-4 rounded bg-white shadow-sm">
    <h3>{{ $job->title }}</h3>
    <p>{{ $job->company->name }}</p>

    @if ($job->is_featured)
        <span class="bg-blue-500 text-white px-2 py-1 rounded"> Featured</span>
    @elseif ($job->is_premium)
        <span class="bg-yellow-500 text-white px-2 py-1 rounded"> Premium</span>
    @endif
</div>
Enter fullscreen mode Exit fullscreen mode

It worked , but it will have a lot of duplicated code accros multiple page especially at this time also i dont use any card component for the job itself .

A small change of design mean that i need to ensure all place is updated also .

Then i discover that blade also can have components .

Blade Component

To know about it you can just read it laravel doc here . Blade component

In this write , im just gonna tell how i use it and implement it to our job portal .

Refactoring Job Card And Badge

Creating a Job Card Component

class JobCard extends Component
{
    public function __construct(public Job $job) {}

    public function label(): ?string
    {
        return match (true) {
            $this->job->is_featured => '🔥 Featured',
            $this->job->is_premium => '🌟 Premium',
            default => null,
        };
    }

    public function labelClass(): string
    {
        return match (true) {
            $this->job->is_featured => 'bg-blue-500',
            $this->job->is_premium => 'bg-yellow-500',
            default => 'bg-gray-300',
        };
    }

    public function variant(): string
    {
        return match (true) {
            $this->job->is_featured => 'border-blue-400 bg-blue-50',
            $this->job->is_premium => 'border-yellow-400 bg-yellow-50',
            default => 'border-gray-200 bg-white',
        };
    }

    public function render()
    {
        return view('components.job-card');
    }
}
Enter fullscreen mode Exit fullscreen mode

Now all conditional already in php and we can add unit test to test this . The blade will only handle the display .

Now our blade look like this

<div class="border p-4 rounded shadow-sm {{ $variant() }}">
    <h3 class="font-semibold text-lg">{{ $job->title }}</h3>
    <p class="text-gray-600">{{ $job->company->name }}</p>

    @if ($label())
        <span class="inline-block mt-2 px-2 py-1 text-xs font-bold text-white rounded {{ $labelClass() }}">
            {{ $label() }}
        </span>
    @endif
</div>
Enter fullscreen mode Exit fullscreen mode

Now you can see no more terniaries , no duplication and easy to read :)

So now we don't need long code anymore whenever we want to display the job advertisement . We just can do like this

<x-job-card :job="$job" />
Enter fullscreen mode Exit fullscreen mode

Closing

Why this is better for me , no need to handle logic in blade , blade focus on display only . It also easier titest . It can be reusable and in future if we want to add more like urgent or sponsore we just need to update in one place only .

Top comments (2)

Collapse
 
xwero profile image
david duymelinck

You don't need a component to make templates reusable, there is also the @include directive .

Also this is a way of working that is backend heavy. When you are working with frontenders that manipulate the templates I would be fine with following template.

@php 
$label = return match (true) {
            $job->is_featured => '🔥 Featured',
            $job->is_premium => '🌟 Premium',
            default => null,
        };
@endphp
@if ($label)
        <span class="inline-block mt-2 px-2 py-1 text-xs font-bold text-white rounded">
            {{ $label }}
        </span>
@endif
Enter fullscreen mode Exit fullscreen mode

As a frontender I would rather work with classes in templates than having to change classes in component class methods.

<div class="border p-4 rounded shadow-sm {{ $job->containerClass }}">

    @if ($label())
        <span class="rounded {{ $job->labelClass }}">
            {{ $label() }}
        </span>
    @endif
</div>
Enter fullscreen mode Exit fullscreen mode

Having a contract, Job, between backend and frontend is a good thing. But make it possible to change things in the frontend without needing backend changes. For example the label, it is fine the text comes from the backend but I would put the emoji in the template.

When I see match(true) and the conditions are no comparisons that is a code smell for me.
Booleans like is_featured and Is_premium can be grouped as an enum.

$label = return match ($job->displayType) {
            DisplayType::featured => '🔥 Featured',
            Displaytype::premium => '🌟 Premium',
            default => null,
        };
Enter fullscreen mode Exit fullscreen mode

When you take the time to create a Job class think of the best way to communicate the intend of the variables.

Collapse
 
killallskywalker profile image
KILLALLSKYWALKER

Thanks a lot for the feedback 🙌 Since I’m not heavy on frontend ! @xwero .
I still use @include but only for separate a template in smaller partials which usually for layout . The problem with @include is that it’s hard to see what variables are going in there. With components you can add @props to spec the variables. If you don’t pass them to you component you will get an exception.