Skip to content

[4.x] Preserve named error bags during component rendering - #10314

Merged
calebporzio merged 8 commits into
livewire:mainfrom
hamdyelbatal122:fix/named-error-bags-lost
Jun 26, 2026
Merged

[4.x] Preserve named error bags during component rendering#10314
calebporzio merged 8 commits into
livewire:mainfrom
hamdyelbatal122:fix/named-error-bags-lost

Conversation

@hamdyelbatal122

@hamdyelbatal122 hamdyelbatal122 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Description

This PR resolves an issue where named error bags passed from session redirects (via .withErrors($validator, 'bag_name')) were lost during Livewire rendering.

Fixes #10306

Root Cause

In SupportValidation.php, Livewire's component validation hooks (render and renderIsland) overwrite the globally shared errors view variable with a new ViewErrorBag containing only the component's default error bag:

$errors = (new ViewErrorBag)->put('default', $this->component->getErrorBag());

This completely discards any previously shared errors (including named error bags) during the rendering of the component, making them inaccessible in Blade via @error('field', 'bag_name') or $errors->getBag('bag_name').

Solution

Instead of overwriting the shared variable with a new empty bag, we retrieve the previously shared errors variable first. If it is a ViewErrorBag, we copy all existing bags over to the new ViewErrorBag before putting the component's default error bag.

I've also added a unit test to verify this behavior.

@ghabriel25

ghabriel25 commented May 28, 2026

Copy link
Copy Markdown
Contributor

@hamdyelbatal122 duplicate PR ? #10307. Although, the approach taken seems different

@joshhanley joshhanley changed the title Fix named error bags from session being lost during rendering [4.x] Preserve named error bags during component rendering Jun 1, 2026

@joshhanley joshhanley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hamdyelbatal122 thanks for the PR! Yeah this is a duplicate of PR #10307, but I think this implementation is better as it doesn't depend on the session. I've copied the test across from that PR as it's more inline with what was reported in the issue.

@hamdyelbatal122

Copy link
Copy Markdown
Contributor Author

Hey @joshhanley, I noticed that the Unit tests for Laravel 13 were failing due to the default validation message format (which doesn't include the trailing period in newer Laravel versions). I've updated the test assertion to check for 'The bar field is required' without the trailing dot to make it compatible across all versions.

The branch has been updated and is ready for the CI runs. Thanks again for the review!

@joshhanley

Copy link
Copy Markdown
Member

@hamdyelbatal122 nah they were failing due to an upstream issue in Symfony and Laravel. It got fixed in last weeks release of Laravel.

@hamdyelbatal122

Copy link
Copy Markdown
Contributor Author

Thanks for the clarification @joshhanley, and for the review. Glad this approach works.

Copilot AI review requested due to automatic review settings June 18, 2026 13:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Livewire rendering regression where previously shared named Laravel validation error bags (e.g. from redirects using withErrors(..., 'bag_name')) were being discarded when Livewire re-shared the global $errors variable during component rendering.

Changes:

  • Preserve any previously shared ViewErrorBag bags when Livewire shares $errors, while still overriding the default bag with the component’s own error bag.
  • Apply the same preservation behavior to both render and renderIsland hooks.
  • Add a unit test that reproduces a non-Livewire POST redirecting back with a named bag and verifies it remains accessible in the rendered component.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/Features/SupportValidation/SupportValidation.php Preserves existing named error bags when sharing $errors into views during component render/island render.
src/Features/SupportValidation/UnitTest.php Adds regression coverage ensuring named session error bags survive the Livewire rendering cycle.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
}

return $errors->put('default', $this->component->getErrorBag());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two thoughts:

  • Why not just use the shared error bag in the first place? aren't we effectively creating like a clone of it?
  • Accessing app('view')->getShared() has a runtime cost. Especially on a page with hundreds of livewire components, that's hundreds of calls to that. (could be memoized - but would have to be cleared for octane-like environments)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @calebporzio ,

Regarding your thoughts:

  1. Using the shared error bag directly vs cloning:
    Since ViewErrorBag is an object, mutating it directly (e.g. by putting the component's own default bag via put('default', ...)) would mutate the globally shared instance. This would cause child or sibling components to overwrite the default error bag for their parent and sibling components. By cloning the shared ViewErrorBag (which creates a shallow copy of the bags array), we can safely override the 'default' bag for the current component's render scope without side-effects on other components, and then safely revert back to the previous instance.

  2. Runtime cost of app('view')->getShared() & Octane compatibility:
    That's a great point. I have updated the code to memoize the view factory in a static property (static::$view) to avoid calling app('view') repeatedly, and hooked into Livewire's flush-state event to reset it, making it completely safe for persistent server environments like Octane.

I have also simplified the code to use clone directly instead of the manual foreach loop. I've pushed these changes to the branch!

@calebporzio

Copy link
Copy Markdown
Collaborator

thanks!

@calebporzio
calebporzio merged commit 6bc0c0b into livewire:main Jun 26, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Livewire rendering cycle does not forward existing named $errors from session

5 participants