livewire/livewire: Exceptions from computed properties now hit the component's exception hook (#10543) questions} {throw new Error("noop")} {
Contributed by ghabriel25
Part of the livewire v4.4.7 release
Livewire PR #10543 closes a long-standing gap in error handling for computed properties. Exceptions thrown inside a #[Computed] method — for example a ModelNotFoundException from a findOrFail() lookup — are now forwarded to the component's exception lifecycle hook, matching the behavior you already get from actions and other lifecycle points. The change lands on the 4.x branch, so if you're coming from v3.8.10 it's part of your upgrade path. No public API changed, but the runtime behavior of existing components can differ, which makes this worth a close read before you upgrade.
What changed
The fix is small and lives entirely inside SupportComputed. Before this PR, evaluating a computed property was a raw, unguarded call:
// Before
protected function evaluateComputed()
{
return invade($this->component)->{parent::getName()}();
}
Because the component was only invade()-ed — a direct call on the instance that bypasses the framework — any Throwable escaping your computed method had no path into the component's exception hook. The PR now evaluates the method inside the framework's component wrapper:
// After
protected function evaluateComputed()
{
$value = null;
wrap($this->component)->tap(function ($component) use (&$value) {
$value = invade($component)->{parent::getName()}();
});
return $value;
}
wrap()->tap() is the same plumbing BaseAuthorize::call() uses to route action exceptions into the component's exception hook, so computed properties now behave consistently with the rest of the framework. A new unit test covers the behavior.
Why it matters
Computed properties are one of the most common places domain exceptions originate: a Post::findOrFail() lookup, a cache layer that throws on a miss, a third-party API call. On v3.8.10 those exceptions sailed straight past the exception hook and surfaced as unhandled rendering errors — there was no way to intercept them at the component level to redirect, abort, log, or fall back to alternative state. Discussion #8373 captures users hitting exactly this. With the fix, a single exception() method on your component becomes a reliable chokepoint for every failure path, including computed properties.
What it looks like in practice
use Livewire\Attributes\Computed;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class PostEditor extends Component
{
public $postId;
#[Computed]
public function post()
{
return Post::findOrFail($this->postId);
}
public function exception($e, $stopPropagation)
{
if ($e instanceof ModelNotFoundException) {
$this->redirectRoute('posts.index');
$stopPropagation();
}
}
public function render()
{
return view('livewire.post-editor', ['post' => $this->post]);
}
}
On v3.8.10 the ModelNotFoundException would blow the request up before the hook ever saw it. After upgrading, the hook receives the exception, and if it calls $stopPropagation(), the exception is swallowed: the request finishes successfully and the computed property simply evaluates to null for that evaluation. If the hook doesn't stop propagation, the exception is rethrown exactly as before, so your global error handling is unaffected.
Why not just wrap() the call itself?
A reasonable question is why the code keeps invade() inside the tap(). The PR note addresses it: replacing invade() with wrap() outright would be simpler, but wrap() routes calls through the component's public API, so protected computed methods would break. Since computed properties are allowed to be protected, the authors kept the raw invade() call for the actual method invocation and used the wrapper only for exception forwarding. That keeps the fix purely additive, with no backward-compatibility risk for how computed properties are declared or called.
Upgrade impact from v3.8.10
- No API changes required. The diff touches only
BaseComputed::evaluateComputed()and the test suite; every existing computed property keeps working as-is. - Audit your existing
exception()hooks. Catch-all handlers — logging, error reporting, generic redirects — will now also receive exceptions coming from computed properties. If your hook wasn't written with those in mind, verify it behaves sensibly when one arrives. - Adjust exception assertions in tests if needed. When a hook calls
$stopPropagation(), the request completes OK instead of throwing, so tests asserting a raw exception from a computed property may need updating. - Expect
nullon swallowed failures. If propagation stops, the computed property resolves tonullrather than leaving the request in a failed state — downstream code should tolerate that.
Takeaways
- Exceptions from
#[Computed]methods now trigger the component'sexceptionhook on 4.x, consistent with actions and the rest of the lifecycle. - The hook receives the throwable plus
$stopPropagation; calling$stopPropagation()swallows the error, lets the request finish, and makes the computed property resolve tonull. - No code changes are required to upgrade; the one action item is auditing existing
exception()hooks so they handle computed-property exceptions intentionally. - Protected computed methods keep working: the fix forwards exceptions via
wrap()->tap()but still invokes the method withinvade().