Skip to content

Upgrade Livewire 2 → 3 - #224

Open
betsyecastro wants to merge 18 commits into
developfrom
update-livewire-3
Open

Upgrade Livewire 2 → 3#224
betsyecastro wants to merge 18 commits into
developfrom
update-livewire-3

Conversation

@betsyecastro

@betsyecastro betsyecastro commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This PR resolves the remaining functional and performance regressions in the Livewire 3 upgrade branch affecting the ProfileStudents, StudentFiler, ProfileDataCard, and TagsModal components. For reference, see the upgrade guide.

Changes include:

After installing Livewire 3, the php artisan livewire:upgrade command was used to address breaking changes progressively, through prompts. Some changes automatically applied across the Livewire components include:

  • Update event dispatching from emit() to dispatch()
  • Convert JavaScript event listeners and error handling to the Livewire 3 APIs
  • wire:model directives update to wire:model.live since in Livewire 3, wire:model is "deferred" by default

Other syntax updates were done manually as follows:

  • Replace $listeners with #[On] attributes
  • Replace getComputedProperty()#[Computed] property()

Issues addressed:

1.RootTagMissingFromViewException on ProfileDataCard component for public profile pages with empty sections:
The ProfileDataCard::render() method returned '' for empty, non-editable sections. Livewire 3 requires exactly one root element. Section partials now always render their root <div> wrapper with the emptiness conditional inside it; the empty-return branch is removed from the component's render() method.

2. Duplicate refresh requests and toasts on student application status change:
Filing a student should produce the following call stack:

  1. StudentFiler->updateStatus()
  2. ProfileStudent->profileStudentStatusUpdated

Instead, the call was:

  1. StudentFiler->updateStatus()
  2. ProfileStudent->profileStudentStatusUpdated
  3. ProfileStudent->profileStudentStatusUpdated

Cause:
The affected child component was declared using the v2 syntax :wire:key attribute:
<livewire:student-filer :wire:key="..." />

For nested Livewire components, Livewire 3 uses :key to establish the component's identity:
<livewire:student-filer :key="..." />

There was also a second problem: the child's key did not change when the student's application status changed. When filing the student changed the status and moved the student-filer component to a different tab pane, Livewire 3 couldn't reconcile the original component instance with the new DOM position, so it created a new instance instead. When that new instance updated, it triggered profileStudentStatusUpdated again, causing the ProfileStudent handler to run a second time.

Fix:
Changed to Livewire 3 :key syntax and added the student's status to the key: $student->slug . '_filer_' . $student->application->status. This makes a status change a clean unmount/remount with no effects carried over. One request, one toast.

3. Student application action icons disappeared and the page became slow/unresponsive after clearing ProfileStudents filters due to a DOM mutation storm caused by dom.watch() and an unscoped morphed hook:
The Livewire hook 'message.processed' was removed and Livewire 3 now uses morphing and the hook was updated to Livewire.hook('morphed', ({ el })), which introduced another issue: after clearing any filter applied on the students applications, the request completed but the page became unresponsive or significantly slow to re-render the DOM elements, even after the dev tools Network tab showed the request as completed.

Diagnosis:
Used DevTools → Performance tab → record → cleared a student app filter → stopped recording after the UI finished rendering, the results showed a long ~34s task, where the majority of the subtasks were called by Run microtasks, with hundreds of repeated, near-identical function-call blocks, that showed the DOM node count went from ~5,725 to ~1,886,717, the event listeners also increased from 302 to 1,784.

image

The call tree shows that MutationCallback accounts for 54% of all samples, almost entirely Livewire's onMutate → initTree → walk/deferHandlingDirectives/directives — Livewire re-scanning and re-initializing subtrees every time the DOM changes.

Cause:
Font Awesome 5's dom.watch() MutationObserver and Livewire 3's own mutation observer fed each other: every morph stripped nested <svg>s, font-awesome re-created them, and those insertions re-triggered Livewire's tree walks.

Fix:
Removed dom.watch(). Icons are converted once at DOMContentLoaded and the v2 message.processed hook was replaced with a morphed hook scoped to the updated component (Livewire.hook('morphed', ({ el }) => dom.i2svg({ node: el }))) that re-converts only the component that just morphed, once per commit, instead of watching the whole document continuously. Reprofiled again: no measurable mutation storm, same ~276 ms Paint-dominated shape. The fix was also verified functionally by manually triggering a status change and a filter clear and confirmed icons survived both.

image

4. Test suite migration and new coverage

  • Data card tests no longer use lastRendered() as public methods (internal-only in v3): assertions use viewData() (re-fetched after each call()) and call('nextPage', $section) for the per-section named paginators — also fixing a latent v2 bug where tests advanced the default page paginator.
  • assertSeeHtmlInOrder fragments rebuilt: v3 injects wire:snapshot/wire:effects/wire:id into root elements and wraps conditionals in <!--[if BLOCK]--> markers, affecting the assertion result. The assertion arguments are checked individually now.
  • Added coverage for StudentFiler::updateStatus and ProfileStudents o verify the basic student application filing interaction, including that the record status is updated and displayed under the correct category and that each category's count is accurate.

5. Updated Tags event listener for student application form:
Fixed the listener to use Livewire.dispatch(event, { tag_type: ... }), switched the direct binding to a delegated $(document).on('change', selector, ...) so it survives Livewire morphs of that region, and collapsed the if/else into a single dispatch call.

Components tested manually:

  1. Bookmarkbutton
  2. DelegationsTable
  3. StudentFeedback

Steps to test:

  1. composer install
  2. docker exec php.profiles php artisan optimize:clear

wunc and others added 10 commits July 7, 2026 17:32
  - Upgrading aws/aws-sdk-php (3.360.0 => 3.388.0)
  - Upgrading barryvdh/reflection-docblock (v2.4.0 => v2.4.1)
  - Upgrading composer/class-map-generator (1.6.2 => 1.7.3)
  - Upgrading composer/pcre (3.3.2 => 3.4.0)
  - Upgrading doctrine/dbal (3.10.3 => 3.10.5)
  - Upgrading doctrine/deprecations (1.1.5 => 1.1.6)
  - Upgrading doctrine/event-manager (2.0.1 => 2.1.1)
  - Upgrading fruitcake/php-cors (v1.3.0 => v1.4.0)
  - Upgrading graham-campbell/result-type (v1.1.3 => v1.1.4)
  - Upgrading guzzlehttp/guzzle (7.10.0 => 7.13.2)
  - Upgrading guzzlehttp/promises (2.3.0 => 2.5.0)
  - Upgrading guzzlehttp/psr7 (2.8.0 => 2.12.3)
  - Upgrading guzzlehttp/uri-template (v1.0.5 => v1.0.8)
  - Upgrading laravel/framework (v10.49.1 => 10.50.2)
  - Upgrading laravel/tinker (v2.10.1 => v2.11.1)
  - Upgrading laravel/ui (v4.6.1 => v4.6.3)
  - Upgrading league/commonmark (2.7.1 => 2.8.2)
  - Upgrading league/flysystem (3.30.2 => 3.35.2)
  - Upgrading league/flysystem-aws-s3-v3 (3.30.1 => 3.35.2)
  - Upgrading league/flysystem-local (3.30.2 => 3.31.0)
  - Upgrading maennchen/zipstream-php (3.2.0 => 3.2.2)
  - Upgrading monolog/monolog (3.9.0 => 3.10.0)
  - Upgrading mtdowling/jmespath.php (2.8.0 => 2.9.2)
  - Upgrading nette/schema (v1.3.3 => v1.3.5)
  - Upgrading nette/utils (v4.0.8 => v4.1.4)
  - Upgrading nikic/php-parser (v4.19.4 => v4.19.5)
  - Upgrading orchestra/sidekick (v1.2.17 => v1.2.20)
  - Upgrading orchestra/testbench-core (v8.39.0 => v8.43.1)
  - Upgrading owen-it/laravel-auditing (v13.7.2 => v13.7.5)
  - Upgrading phpdocumentor/reflection-docblock (5.6.3 => 5.6.7)
  - Upgrading phpdocumentor/type-resolver (1.10.0 => 1.12.0)
  - Upgrading phpoption/phpoption (1.9.4 => 1.9.5)
  - Upgrading phpstan/phpdoc-parser (2.3.0 => 2.3.2)
  - Upgrading phpunit/phpunit (10.5.58 => 10.5.64)
  - Upgrading psalm/plugin-laravel (v2.12.1 => v2.12.3)
  - Upgrading psy/psysh (v0.12.14 => v0.12.24)
  - Upgrading ramsey/uuid (4.9.1 => 4.9.3)
  - Upgrading sebastian/comparator (5.0.4 => 5.0.5)
  - Upgrading sentry/sentry (4.18.1 => 4.29.0)
  - Upgrading sentry/sentry-laravel (4.19.0 => 4.26.0)
  - Upgrading spatie/array-to-xml (3.4.1 => 3.4.4)
  - Upgrading spatie/backtrace (1.8.1 => 1.8.2)
  - Upgrading spatie/browsershot (5.1.0 => 5.4.0)
  - Upgrading spatie/db-dumper (3.8.0 => 3.8.3)
  - Upgrading spatie/eloquent-sortable (4.5.2 => 5.0.1)
  - Upgrading spatie/flare-client-php (1.10.1 => 1.11.1)
  - Upgrading spatie/ignition (1.15.1 => 1.16.0)
  - Upgrading spatie/image-optimizer (1.8.0 => 1.10.0)
  - Upgrading spatie/laravel-package-tools (1.92.7 => 1.93.1)
  - Upgrading spatie/laravel-tags (4.10.1 => 4.12.0)
  - Upgrading spatie/temporary-directory (2.3.0 => 2.4.0)
  - Upgrading symfony/console (v6.4.27 => v6.4.42)
  - Upgrading symfony/css-selector (v7.3.6 => v7.4.9)
  - Upgrading symfony/deprecation-contracts (v3.6.0 => v3.7.1)
  - Upgrading symfony/error-handler (v6.4.26 => v6.4.36)
  - Upgrading symfony/event-dispatcher (v7.3.3 => v7.4.14)
  - Upgrading symfony/event-dispatcher-contracts (v3.6.0 => v3.7.1)
  - Upgrading symfony/filesystem (v7.3.6 => v7.4.11)
  - Upgrading symfony/finder (v6.4.27 => v6.4.42)
  - Upgrading symfony/http-foundation (v6.4.29 => v6.4.42)
  - Upgrading symfony/http-kernel (v6.4.29 => v6.4.42)
  - Upgrading symfony/mailer (v6.4.27 => v6.4.40)
  - Upgrading symfony/mime (v6.4.26 => v6.4.41)
  - Upgrading symfony/options-resolver (v7.3.3 => v7.4.8)
  - Upgrading symfony/polyfill-ctype (v1.33.0 => v1.37.0)
  - Upgrading symfony/polyfill-intl-grapheme (v1.33.0 => v1.38.1)
  - Upgrading symfony/polyfill-intl-idn (v1.33.0 => v1.38.1)
  - Upgrading symfony/polyfill-intl-normalizer (v1.33.0 => v1.38.0)
  - Upgrading symfony/polyfill-mbstring (v1.33.0 => v1.38.2)
  - Upgrading symfony/polyfill-php80 (v1.33.0 => v1.37.0)
  - Upgrading symfony/polyfill-php83 (v1.33.0 => v1.38.2)
  - Upgrading symfony/polyfill-uuid (v1.33.0 => v1.37.0)
  - Upgrading symfony/process (v6.4.26 => v6.4.41)
  - Upgrading symfony/psr-http-message-bridge (v7.3.0 => v7.4.8)
  - Upgrading symfony/routing (v6.4.28 => v6.4.41)
  - Upgrading symfony/service-contracts (v3.6.1 => v3.7.1)
  - Upgrading symfony/string (v7.3.4 => v7.4.13)
  - Upgrading symfony/translation (v6.4.26 => v6.4.42)
  - Upgrading symfony/translation-contracts (v3.6.1 => v3.7.1)
  - Upgrading symfony/uid (v6.4.24 => v6.4.32)
  - Upgrading symfony/var-dumper (v6.4.26 => v6.4.42)
  - Upgrading tijsverkoyen/css-to-inline-styles (v2.3.0 => v2.4.0)
  - Upgrading vlucas/phpdotenv (v5.6.2 => v5.6.4)
  - Upgrading voku/portable-ascii (2.0.3 => 2.1.1)
  - Upgrading webmozart/assert (1.12.1 => 2.4.1)
- Removing intervention/image (2.7.2)
  - Removing league/glide (2.3.2)
  - Upgrading spatie/image (2.2.7 => 3.9.5)
  - Upgrading spatie/laravel-medialibrary (10.15.0 => 11.23.1)
- mostly docblock stuff
- updates psalm-ignore with latest
@betsyecastro betsyecastro self-assigned this Aug 3, 2026
@betsyecastro betsyecastro added 📦️ dependencies Update, change, or remove a dependency ☕️ javascript Pull requests that update Javascript code ⬆️ priority:high High priority issue labels Aug 3, 2026
@betsyecastro
betsyecastro requested a review from wunc August 12, 2026 21:35
@betsyecastro
betsyecastro marked this pull request as ready for review August 12, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📦️ dependencies Update, change, or remove a dependency ☕️ javascript Pull requests that update Javascript code ⬆️ priority:high High priority issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants