Skip to content
LaravelBlog

Changelog for livewire/livewire: PR #10499 — [4.x] Consolidate renderless setup to HandlesRenderless

LaravelBlogBot
LaravelBlogBot

Contributed by ghabriel25

Part of the livewire v4.4.7 release

Livewire 4 keeps tightening its internals, and PR #10499 is a tidy example: every place that used to read or write the internal "should we skip the render?" flags now goes through the HandlesRenderless trait, making it the single source of truth for renderless behavior. If you're upgrading from v3.8.10, the change is almost entirely invisible — #[Renderless], skipRender(), lazy loading, nested components, and slots all behave exactly as they did before. What you gain is a small, formal API (skipIslandsRender(), shouldSkipRender(), shouldSkipIslandsRender()) and a codebase that no longer scatters store-key magic strings across five different features.

What changed

Renderless state has always lived in each component's internal store — a key/value bag used by Livewire's features. Until now, several subsystems reached into that bag directly instead of exposing intent-revealing methods:

// Before — scattered across islands, slots, nesting, and the render pipeline:
store($component)->get('skipRender', false);
$this->storeGet('skipIslandsRender', false);
$this->storeSet('skipIslandsRender', true);

This PR adds three methods to HandlesRenderless and routes every call site through them:

public function skipIslandsRender()
{
    store($this)->set('skipIslandsRender', true);
}

public function shouldSkipRender()
{
    return store($this)->get('skipRender', false);
}

public function shouldSkipIslandsRender()
{
    return store($this)->get('skipIslandsRender', false);
}

The updated call sites read like a tour of the render pipeline: the Islands feature now asks $this->component->shouldSkipIslandsRender(), nested components and Slots check shouldSkipRender(), the #[Renderless] attribute calls skipIslandsRender(), and the core HandleComponents::render() step calls $component->shouldSkipRender(). Fittingly, HandleComponents.php no longer even imports the store() helper.

Why it matters

The old approach meant knowledge of the skipRender and skipIslandsRender store keys was duplicated in five files. Any change to those conventions — a rename, a new default, extra bookkeeping — had to be hunted down across the codebase, and one missed spot produces subtle bugs (say, an action that skips the main render but still re-renders islands).

Consolidating on the trait buys two things. Internally, there is one place where renderless behavior evolves. Externally, plugin and advanced users get a stable, method-based API instead of depending on internal store conventions that were never meant to be public.

No behavior change for #[Renderless]

This is the part most apps care about, and the answer is: nothing changes. A renderless action still skips HTML generation while returning data to the client:

use Livewire\Attributes\Renderless;
use Livewire\Component;

class ReportExporter extends Component
{
    #[Renderless]
    public function export()
    {
        dispatch(new ExportJob($this->reportId));

        // The action's result is sent back to the browser,
        // but neither the view nor its islands re-render.
    }
}

Under the hood, the attribute's implementation just got cleaner:

// Before:
$this->storeSet('skipIslandsRender', true);
$this->component->skipRender();

// After:
$this->component->skipIslandsRender();
$this->component->skipRender();

Same net effect: the update round-trips data without re-rendering HTML.

New tools for plugin and advanced code

Because the trait is used by Livewire's component core, the new methods are callable from any component or supporting class. Semantics worth knowing:

  • shouldSkipRender() returns false, true, or a string of pre-rendered HTML — skipRender($html) accepts HTML, so the check is truthy either way.
  • shouldSkipIslandsRender() tells you whether island rendering has been opted out of for this request.
  • skipIslandsRender() lets you deliberately leave embedded islands untouched during an update, even when the main render still happens:
public function refreshTimestampOnly()
{
    $this->skipIslandsRender(); // islands stay as-is this cycle
}

If you previously poked the store directly in a plugin — store($component)->set('skipRender', true) — that still works, since the trait uses the same keys underneath. But switch to the methods: the store keys are now officially internal.

Upgrade impact from v3.8.10

  • Typical apps: nothing to do. #[Renderless], skipRender(), lazy loading, nested components, and slots all behave identically; your test suite should not notice this change.
  • Islands are new in v4, so there is nothing to migrate — skipIslandsRender simply did not exist in v3.
  • One real edge case: PHP resolves trait/class method collisions in favor of the class. If your app has a base component that already defines shouldSkipRender(), shouldSkipIslandsRender(), or skipIslandsRender(), it will silently override Livewire's implementation and can break render skipping. Rename those methods before upgrading.
  • Plugin authors: migrate any direct store() reads/writes of skipRender or skipIslandsRender to the new methods to stay forward-compatible.

Takeaways

  • PR #10499 makes HandlesRenderless the single source of truth for renderless handling in Livewire 4.
  • New public methods: skipIslandsRender(), shouldSkipRender(), and shouldSkipIslandsRender(), available on every component.
  • Zero behavior change for #[Renderless] and the render pipeline — this is a refactor with API benefits, not a fix.
  • Before upgrading from v3.8.10, check custom base components for method-name collisions with the new trait methods.

Sources

More from this release

Related Articles