livewire/livewire: BaseRenderless moves into a dedicated SupportRenderless feature (PR #10346)
Contributed by ghabriel25
Part of the livewire v4.4.7 release
This pull request is an internal housekeeping change on the Livewire v4 line: BaseRenderless, the base class that powers renderless actions, has been relocated from the low-level Mechanisms namespace into a dedicated feature namespace. There is no behavior change — the #[Renderless] attribute and the wire:click.renderless modifier behave exactly as before. The change matters mainly to framework extension authors who reference BaseRenderless directly, and it arrives automatically as part of your upgrade from 3.8.10 to v4. As a bonus, the PR introduces browser tests that pin down the "don't call render()" behavior for both supported styles of renderless actions.
What changed
The core of the PR is a file move and namespace change:
Before: Livewire\Mechanisms\HandleComponents\BaseRenderless
After: Livewire\Features\SupportRenderless\BaseRenderless
On disk, src/Mechanisms/HandleComponents/BaseRenderless.php became src/Features/SupportRenderless/BaseRenderless.php. The two internal consumers were updated to point at the new location: the public Livewire\Attributes\Renderless attribute (which extends BaseRenderless) and the SupportEvents hook. The PR also adds a new BrowserTest.php under Features/SupportRenderless with two scenarios: one where a method is marked with the renderless attribute, and one where the wire:click.renderless modifier is used on its own.
Why the move matters
Livewire v4 organizes its source into two conceptual buckets. Mechanisms/ holds the low-level engine plumbing — request handling, component hydration — while Features/Support* directories hold discrete, user-facing capabilities like SupportEvents and SupportAttributes. BaseRenderless is the engine behind a user-facing capability: it is what the public #[Renderless] attribute extends. Sitting under Mechanisms/HandleComponents made it what the PR author called "an orphan feature."
Moving it into Features/SupportRenderless puts it alongside related feature code and mirrors the pattern already used for the Async attribute. For extension authors, this makes the framework's extension surface easier to discover: if you're building a custom attribute that skips rendering, its base class now lives in a predictable, parallel location to the other feature base classes.
What stays the same for your app
The public API is untouched. Everything you know from 3.8.10 about renderless actions still applies:
use Livewire\Attributes\Renderless;
use Livewire\Component;
class ProductGallery extends Component
{
public $renderCount = 0;
#[Renderless]
public function preview(string $sku)
{
// Runs on the server, but skips render() entirely
}
public function render()
{
$this->renderCount++;
return view('livewire.product-gallery');
}
}
{{-- Neither action triggers a re-render of the component --}}
<img wire:mouseenter="preview('{{ $sku }}')">
<button wire:click.renderless="preview('{{ $sku }}')">Quick view</button>
The new browser tests verify exactly this contract: a component renders a counter, the user clicks a button wired to a renderless action, and the counter must not change — proving render() was skipped. That's now covered for both the attribute pathway and the modifier pathway.
Upgrade impact: who needs to act
For the vast majority of applications coming from v3.8.10, there is nothing to do. If you use #[Renderless] on methods or wire:click.renderless in your Blade templates, the upgrade is seamless.
The only group that needs to act is developers who reference BaseRenderless directly — typically to build a custom attribute with renderless behavior. If that's you, update the import:
// Before
use Livewire\Mechanisms\HandleComponents\BaseRenderless;
#[\Attribute]
class SilentTracking extends BaseRenderless
{
//
}
// After
use Livewire\Features\SupportRenderless\BaseRenderless;
#[\Attribute]
class SilentTracking extends BaseRenderless
{
//
}
The old class name no longer exists in v4, so a stale reference will fail with a "class not found" error. A quick search catches any lingering references:
grep -rn 'BaseRenderless' app resources tests
Also check string-based references — static analysis baselines, IDE metadata, or anything that resolves the class by name — since those won't fail loudly until runtime.
Takeaways
- Internal refactor only: renderless actions behave identically to before.
BaseRenderlessnow lives atLivewire\Features\SupportRenderless\BaseRenderless.- The public API —
#[Renderless]andwire:click.renderless— is unchanged. - Action required only if you extend or reference the base class directly; update the namespace.
- New browser tests lock in the "
render()is skipped" guarantee for both the attribute and the modifier.