Upgrade Livewire 2 → 3 - #224
Open
betsyecastro wants to merge 18 commits into
Open
Conversation
- 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
…o ensure a root element
betsyecastro
marked this pull request as ready for review
August 12, 2026 21:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:upgradecommand was used to address breaking changes progressively, through prompts. Some changes automatically applied across the Livewire components include:emit()todispatch()wire:modeldirectives update towire:model.livesince in Livewire 3,wire:modelis "deferred" by defaultOther syntax updates were done manually as follows:
$listenerswith #[On] attributesgetComputedProperty()→#[Computed] property()Issues addressed:
1.
RootTagMissingFromViewExceptiononProfileDataCardcomponent 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'srender()method.2. Duplicate refresh requests and toasts on student application status change:
Filing a student should produce the following call stack:
StudentFiler->updateStatus()ProfileStudent->profileStudentStatusUpdatedInstead, the call was:
StudentFiler->updateStatus()ProfileStudent->profileStudentStatusUpdatedProfileStudent->profileStudentStatusUpdatedCause:
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
profileStudentStatusUpdatedagain, causing theProfileStudenthandler to run a second time.Fix:
Changed to Livewire 3
:keysyntax 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
ProfileStudentsfilters due to a DOM mutation storm caused bydom.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.The call tree shows that
MutationCallbackaccounts 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 atDOMContentLoadedand the v2message.processedhook 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.4. Test suite migration and new coverage
lastRendered()as public methods (internal-only in v3): assertions useviewData()(re-fetched after eachcall()) andcall('nextPage', $section)for the per-section named paginators — also fixing a latent v2 bug where tests advanced the defaultpagepaginator.assertSeeHtmlInOrderfragments rebuilt: v3 injectswire:snapshot/wire:effects/wire:idinto root elements and wraps conditionals in<!--[if BLOCK]-->markers, affecting the assertion result. The assertion arguments are checked individually now.StudentFiler::updateStatusandProfileStudentso 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:
Steps to test:
composer installdocker exec php.profiles php artisan optimize:clear