Skip to content

fix(mobx): don't track observable set mutations in the running derivation - #4765

Merged
kubk merged 1 commit into
mobxjs:mainfrom
b84330808:fix-set-mutations-tracked
Nov 6, 2026
Merged

kubk merged 1 commit into
mobxjs:mainfrom
b84330808:fix-set-mutations-tracked

Conversation

@b84330808

Copy link
Copy Markdown
Contributor

ObservableSet.add and delete check membership through the public has(), which reports the set as observed. So a derivation that only writes to a set gets subscribed to it, and replace goes through the same path. It hurts most with replace, because the re-run undoes the other change:

const s = observable.set([1])
autorun(() => {
    s.replace([1, 2])
})
s.add(3) // re-runs the autorun, which removes 3 again

Observable maps and arrays don't do this: map.set uses a private has_, and map.js already tests that map.clear isn't tracked. With observableRequiresReaction on, plain add/delete calls also warned that the set was being read outside a reactive context.

This adds the same private has_ to ObservableSet. has() itself still subscribes. Production cjs bundle: +22 bytes raw, -1 byte gzipped.

Code change checklist

  • Added/updated unit tests
  • Updated /docs. For new functionality, at least API.md should be updated
  • Verified that there is no significant performance drop (npm -w mobx run test:performance)

…tion

ObservableSet.add and delete checked membership through the public has(), which
reports the set as observed. So a derivation that only writes to a set got
subscribed to it, and replace went through the same path. Observable maps and
arrays don't do this.
@changeset-bot

changeset-bot Bot commented Nov 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c96dfa2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mobx Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kubk
kubk requested a balanced review from Copilot November 6, 2026 05:19

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.

🟢 Approval recommended

No active review issues are identified for the change preventing observable set mutations from creating unintended subscriptions.

No open issues 🎉

What changed in this PR

This PR prevents observable set mutations from unintentionally subscribing the running derivation to the set, aligning their behavior with observable maps and arrays.

Changes:

  • Adds a private, non-tracking membership check for add and delete, also fixing tracking through replace.
  • Adds regression tests for untracked mutations and verifies that public has() remains reactive.
  • Adds a patch changeset documenting the fix.
File Description
packages/​mobx/​src/​types/​observableset.ts Separates internal membership checks from public reactive reads.
packages/​mobx/​__tests__/​base/​set.js Tests mutation tracking behavior and preserves has() reactivity.
.changeset/​observable-set-mutations-untracked.md Records the observable set fix for a patch release.

Review effort: Balanced

@kubk

kubk commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Thanks, it seems the bug has been sitting there since observable sets were implemented

@kubk
kubk merged commit c99023b into mobxjs:main Nov 6, 2026
@github-actions github-actions Bot mentioned this pull request Nov 6, 2026
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.

3 participants