mauricius / laravel-htmx

Laravel helper library for Htmx
MIT License
298 stars 14 forks source link

Fragment not found in Nested component. #20

Open sbrow opened 1 month ago

sbrow commented 1 month ago

RuntimeException: No fragment called 'pagination' exists

So I recently abstracted a view so I could share code between models, but ran into a snag where the fragments are no longer found.

Here is my code, simplified for clarity:

{{-- resources/views/campaigns/show.blade.php --}}
@php
$responses = $campaign->responses()->paginate(12);
@endphp
<x-gallery.show :gallery="$campaign" :pagination="$responses->links()">
  @foreach($responses as $response)
    <div>{{ $response->text }}</div>
  @endforeach
</x-gallery.show>
{{-- resources/views/components/gallery/show.blade.php --}}
<div class="row">
  <div class="col-auto">
    <div id="pagination" hx-swap-oob="true">
      {!! $pagination !!}
    </div>
  </div>
</div>

<div class="row">
  @fragment('items')
    <div class="col" id="items" hx-swap-oob="true">
      {{ $slot ?? '' }}
    </div>
  @endfragment
</div>
<?php
// app/Http/Controllers/CampaignController.php

namespace App\Http\Controllers;

use Mauricius\LaravelHtmx\Http\HtmxResponse;

class CampaignController {
  public function show() {
    $data = [ /* elided */ ];
    return (new HtmxResponse)
      ->addFragment('campaigns.show', 'pagination', $data)
      ->addFragment('campaigns.show', 'items', $data);
  }
}

I will see if I can find a way to fix this in a PR.

mauricius commented 1 month ago

The code is not super clear, but I suspect that the problem is that the package does not compile the whole view, but only the parts needed to render fragments. In your case the show.blade.php view does not contain pagination fragment (in your example is not even marked as a fragment, but that's another story) nor the items fragment.

Instead, you should compile those fragments from the gallery/show.blade.php view.

sbrow commented 1 month ago

Thanks for the response. I tried using (new HtmxResponse)->addFragment('gallery.show', 'pagination', $data), but it didn't work out. Fortunately, simply switching my CampaignController to use "default" fragments did the trick.

<?php
// app/Http/Controllers/CampaignController.php

namespace App\Http\Controllers;

use Mauricius\LaravelHtmx\Http\HtmxResponse;

class CampaignController {
  public function show() {
    $data = [ /* elided */ ];
    return view('campaigns.show', $data)
      ->fragments( 'pagination', 'items');
  }
}

Since (IIUC) the whole purpose of HtmxResponse::addFragment is to be a performance improvement over the default view()->fragments() as stated in #8, I think there's an argument to be made over whether this problem is a bug, or whether it's outside the scope of the package to handle this use case.

If it's the latter, feel free to close the issue :smile:

mauricius commented 1 month ago

Thanks for the response. I tried using (new HtmxResponse)->addFragment('gallery.show', 'pagination', $data), but it didn't work out.

That's probably because in your example you haven't marked pagination as fragment. It should be

<div class="row">
  <div class="col-auto">
    @fragment('pagination')
      <div id="pagination" hx-swap-oob="true">
        {!! $pagination !!}
      </div>
    @endfragment
  </div>
</div>

But the $pagination variable is passed as a prop into the component from the parent view, but since now you're rendering the view in isolation, you have to pass the same data when you return the fragment from the controller.