Skip to content

fix(signals): avoid reading unrelated state slices in patchState - #5212

Open
97Fakhreddine wants to merge 1 commit into
ngrx:mainfrom
97Fakhreddine:fix/5183-patch-state-unrelated-slices
Open

fix(signals): avoid reading unrelated state slices in patchState#5212
97Fakhreddine wants to merge 1 commit into
ngrx:mainfrom
97Fakhreddine:fix/5183-patch-state-unrelated-slices

Conversation

@97Fakhreddine

Copy link
Copy Markdown

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[x] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] Other

What is the current behavior?

patchState currently reads the complete state through getState before applying an update.

Because getState evaluates every state slice, patching an unrelated property can fail when another state slice throws while being read, for example a slice backed by a resource or linkedSignal.

This means a call such as:

patchState(store, { name: 'John' });

can be blocked by an unrelated state slice even though that slice is not part of the update.

Closes #5183

What is the new behavior?

For partial state object updates, patchState no longer eagerly reads the complete state.

Instead, it only reads the state slices that are actually being updated when it needs to compare their current values.

Functional updaters still receive the complete current state as before:

patchState(store, (state) => ({
  count: state.count + 1,
}));

This preserves the existing behavior of functional updaters while allowing independent state slices to be patched even when another unrelated slice cannot currently be read.

Implementation

The change distinguishes between two cases:

  • Partial state object updates do not require the complete current state.
  • Functional state updaters still require the complete state and therefore continue to use getState.

For partial object updates, the current value is read directly from the corresponding signal before deciding whether the signal needs to be updated.

This also preserves the existing equality check so unchanged state slices are not written unnecessarily.

Tests

A regression test was added that creates:

  • a writable name state slice
  • an unrelated linked state slice that throws when accessed

The test verifies that:

patchState(stateSource, { name: 'John' });

does not read the throwing slice, does not throw, and successfully updates name.

The Signals test suite passes with the change:

pnpm exec nx test signals --watchAll=false

Does this PR introduce a breaking change?

[ ] Yes
[x] No

The behavior of functional updaters remains unchanged. The change only prevents unnecessary reads of unrelated state slices when using partial state object updates.

@netlify

netlify Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploy Preview for ngrx-io ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 4da1096
🔍 Latest deploy log https://app.netlify.com/projects/ngrx-io/deploys/6a8783411ae7b60008e41756
😎 Deploy Preview https://deploy-preview-5212--ngrx-io.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

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.

@ngrx/signals: patchState blocks when State signal throws an error

1 participant