livewire/livewire: reset() no longer unsets form objects
Contributed by ghabriel25
Part of the livewire v4.4.7 release
Upgrading from v3.8.10, you pick up a targeted bug fix from pull request #10554: calling $this->reset() — or $this->reset('form') — on a component that uses a form object could unset the form property entirely instead of resetting its fields. The next read of $this->form then failed with Livewire\Exceptions\PropertyNotFoundException, usually mid-render. The fix, merged into 4.x and backported to 3.x, teaches reset() to recognize form objects and delegate to the form's own reset() method. There are no API changes, so the upgrade itself is a plain composer update.
What changed
The reset() implementation in InteractsWithProperties works by instantiating a fresh copy of the component and using reflection to decide, per property, whether it should be restored to its default or forgotten. That logic had a blind spot for form objects, which this patch closes. Before the reflection check runs, reset() now checks whether the live property is a form object and, if so, delegates to it:
// src/Concerns/InteractsWithProperties.php
if (isset($this->{$property}) && is_subclass_of($this->{$property}, Form::class)) {
$this->{$property}->reset();
continue;
}
Because both $this->reset() (reset everything) and $this->reset('form') (reset one named property) funnel through this code path, both forms of the call are fixed at once. Dotted-path resets such as $this->reset('form.title') already delegated into the form correctly and are unchanged.
The bug, and why it happened
Form objects are declared as typed properties with no default value:
public MyForm $form;
That declaration style is exactly what tripped reset(). To restore defaults, reset() builds a fresh instance of the component and asks reflection whether each property is initialized there. On that fresh instance the form property is — by design — uninitialized, so the old code concluded there was nothing to preserve and ran data_forget($this, 'form'), removing the property from the live component. The irony is that Livewire initializes the form on your actual component; the check just consulted the wrong instance.
A minimal reproduction from the PR:
class MyForm extends Form
{
public $title = 'My Title';
}
new class extends Component
{
public MyForm $form;
public function save()
{
$this->reset(); // previously unset $this->form entirely
}
};
<div>
<div>{{ $form->title }}</div>
<button wire:click="save">Save</button>
</div>
Clicking "Save" reset the component, and the next render then blew up because $form no longer existed. This was reported in discussion #9449.
The fix in detail
The new guard runs against the live component — where Livewire's form-object support has already initialized $this->form — rather than the throwaway instance. If the property is a subclass of Livewire\Form, Livewire calls the form's own reset(), which restores the form's public properties to their defaults, then continues past the unset logic. Non-form properties keep their existing behaviour, including genuinely uninitialized ones being forgotten.
After the patch, the flow covered by the new regression test (test_resetting_component_does_not_unset_form_object) looks like this:
->set('form.title', 'Some Title')
->call('resetAll') // $this->reset()
->assertSetStrict('form.title', 'foo') // restored to the default
->assertSetStrict('form', fn ($form) => $form instanceof PostFormStubWithDefaults)
The form object survives, its fields return to their defaults, and unrelated properties in the same component still reset as expected. The test asserts the same outcome for $this->reset('form'), so both entry points are locked in.
Upgrade impact
For nearly everyone this is strictly good news, and there is nothing to migrate:
- No API changes —
reset()and form objects keep their signatures; this is a drop-in patch. - Behaviour changes only in the previously broken scenario: an exception becomes the intended "reset fields to defaults" result.
- If you worked around the bug by calling
$this->form->reset()manually, avoidingreset()on form-based components, or resetting fields one by one via dotted paths, those workarounds keep working but can now be simplified back to a plain$this->reset(). - The only theoretical edge case is code that depended on
reset()unsetting the form property — but that path threwPropertyNotFoundExceptionon the next access anyway, so it could not have worked in practice.
The fix is merged for 4.x and backported to 3.x, so any 3.x release after v3.8.10 includes it. Update with composer update livewire/livewire and the new behaviour applies immediately, with no configuration or code changes required.
Key takeaways
$this->reset()and$this->reset('form')no longer unset form objects; they reset the form's fields to their defaults and keep the object intact.- Fixes the
PropertyNotFoundExceptionthat occurred when touching$this->formafter a full or named reset. - Root cause: form objects are typed properties without defaults, which
reset()misread as "uninitialized, so forget it" on its fresh comparison instance. - No upgrade steps beyond pulling a patched version (any 3.x release after v3.8.10, or 4.x).
- A regression test now covers both reset-all and per-property resets involving form objects.