Skip to content
LaravelBlog

Livewire v3.6.4

LaravelBlogBot
LaravelBlogBot

Contributed by calebporzio

Part of the livewire v4.4.7 release

If you're upgrading from v3.8.10, loading state around navigate: true redirects had a blind spot: the moment a Livewire request finished, every spinner, disabled button, and read-only input was released — even when that request's final act was a redirect whose destination page still had to be fetched. Pull request #10539 fixes this by treating a same-origin navigate redirect as part of the action's loading lifecycle. Developer-declared wire:loading state, automatic data-loading indicators, and form protection now stay active until the destination is ready or the navigation is cancelled, while transient busy state is kept out of browser history so Back and Forward always restore a clean page. There are no API changes — this is purely a behavior fix.

The gap between request and page swap

On v3.8.10, the sequence for a redirected action looked like this: the user clicks "Save", the button disables and the spinner appears, the commit succeeds, and the server responds with a redirect effect telling the client to navigate. At that point the request lifecycle is technically over, so loading cleanup fired on onFinish — the button re-enabled, the spinner vanished, and form inputs became editable again. Meanwhile, the Navigate plugin still had to fetch the destination HTML, which on a slow route can take seconds. The result was a dead zone where the page looked idle but the app was mid-flight, and worse, a re-enabled submit button inviting double submissions.

A navigation lifecycle owned by Navigate

This PR gives Navigate ownership of a small transaction with four meaningful phases: start (once the cancelable navigate event is accepted), ready (destination HTML is available, before the departing page is serialized into history), cancelled (the fetch fails or is aborted), and finished (the new page has initialized and alpine:navigated fires).

The loading features no longer need to know anything about redirect or navigate effects. wire:loading watches a narrow window — after a successful response, before its effects are applied — for a navigation starting. If one does, cleanup is handed to the transaction via onDestinationSettled(), which fires exactly once when the destination is ready or cancelled. If no navigation starts, everything clears exactly as before on request finish. The automatic request-owned busy states (the data-loading marker, submit-button disabling, read-only form inputs) use the same handoff through a shared releaseAfterNavigation() helper, and the cleanup happens in the ready phase — before the departing page is snapshotted — so Back/Forward navigation restores the original, non-loading page.

What it looks like in your app

<form wire:submit="save">
    <input type="text" wire:model="name">

    <button type="submit">Save</button>
</form>
public function save()
{
    // ...

    $this->redirect(route('dashboard'), navigate: true);
}

On v3.8.10, the button re-enables and the input becomes editable as soon as the save commit resolves, leaving a quiet gap until the dashboard swaps in. With this release, the data-loading marker, the disabled button, and the read-only input all persist for the entire destination fetch, then clear the instant the new page takes over. If the navigation is prevented by a livewire:navigate listener calling event.preventDefault(), or the fetch is aborted, loading clears immediately and the current page stays fully interactive. Cross-origin redirects are unchanged — Navigate still hands off to the browser for a native visit.

Upgrade impact

  • No code changes required. There are no new directives, hooks, or events; upgrade and the behavior improves.
  • Ordinary actions are untouched. Actions that don't redirect, and redirects without navigate: true, still clear loading on request finish.
  • Loading lasts longer for navigate redirects. If you built workarounds for the dead zone — JavaScript timers keeping spinners alive, listening to alpine:navigating to re-apply busy state, or custom double-submit guards for redirect flows — you can now remove them.
  • No global page lock. Only elements already participating in loading behavior are held; nothing else on the page is blocked.
  • No stale state across navigations. Starting a new navigation cancels the previous transaction, and listeners are cleaned up on every path, verified by a successful retry after an aborted navigation in the test suite.

Under the hood, the change is covered by ten new browser tests with 67 assertions, spanning delayed destination fetches, synchronized visual and disabled states, protected form controls, clean Back/Forward restoration, prevention, cancellation, and recovery.

Takeaways

  • wire:loading, automatic data-loading indicators, submit-button disabling, and read-only form inputs now stay active through a same-origin navigate: true redirect until the destination is ready or the navigation is cancelled or prevented.
  • Transient loading state is cleared before the departing page is saved to history, so Back/Forward restores a clean page.
  • Only Navigate interprets redirect effects; loading features simply defer cleanup to the navigation transaction.
  • Upgrading from v3.8.10 requires no code changes — and is a good moment to delete any double-submit or spinner-persistence workarounds.

Sources

More from this release

Related Articles