Laravel shorts – Blade component gotcha

Blade components are a great tool for structuring your view templates into small reusable pieces. But while working with them, I stumbled upon a gotcha that I think you should know about. First, let’s take a look at this anonymous component:

post-title.blade.php

@props(['post'])
<h1 {{ $attributes }}>{{ $post->title }}</h1>

And here is how it is used in, say, a post list page:

@foreach ($posts as $post)
  <x-post-title :post="$post" />
@endforeach

In this example, the posts are Eloquent models that were previously loaded from the database. Now, if you run this code, you will notice nothing strange in particular. Everything works as intended. However, if you dig a little deeper, you will find some unexpected behavior:

Every time the component is instantiated, the post model is being serialized to JSON.

The cause for this behavior lies in the way the Blade component compiler processes attributes inside the sanitizeComponentAttribute function.

CompilesComponents.php

/**
 * Sanitize the given component attribute value.
 *
 * @param  mixed  $value
 * @return mixed
 */
public static function sanitizeComponentAttribute($value)
{
    return is_string($value) ||
           (is_object($value) && ! $value instanceof ComponentAttributeBag && method_exists($value, '__toString'))
                    ? e($value)
                    : $value;
}

Basically, any object that implements a __toString function will be converted to a string. In the case of an Eloquent model, this will convert it to JSON. Depending on how large your model is and how often the component is used, this will waste a significant amount of resources on constructing JSON strings that are never used.

Solution: Use class based components

Instead of an anonymous component, use a class based component and add your props to the constructor arguments. After you do that, your objects will no longer be converted to strings.

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class PostTitle extends Component
{
    public $post;

    public function __construct($post)
    {
        $this->post = $post;
    }

    public function render()
    {
        return view('components.post-title');
    }
}

Update 2021-10-22
An issue has been created on GitHub: https://github.com/laravel/framework/issues/39232

And a fix has been merged:
https://github.com/laravel/framework/pull/39319

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.