Skip to content

fix(reactivity): handle objects with custom Symbol.toStringTag (fix #10483)#14874

Open
ATOM00blue wants to merge 1 commit into
vuejs:mainfrom
ATOM00blue:fix/reactive-custom-tostringtag
Open

fix(reactivity): handle objects with custom Symbol.toStringTag (fix #10483)#14874
ATOM00blue wants to merge 1 commit into
vuejs:mainfrom
ATOM00blue:fix/reactive-custom-tostringtag

Conversation

@ATOM00blue
Copy link
Copy Markdown

@ATOM00blue ATOM00blue commented May 22, 2026

Fixes #10483

Problem

reactive() / ref() silently fail to make an object reactive when that object (or a subclass of a built-in collection) defines a custom Symbol.toStringTag.

getTargetType decides whether a value should be wrapped in a proxy based on toRawType(value), which derives the type from Object.prototype.toString.call(value). When a custom Symbol.toStringTag is present, that call returns e.g. "[object Goat]" instead of "[object Object]", so toRawType returns "Goat", targetTypeMap falls through to INVALID, and the value is returned untouched (not reactive).

This is a real-world issue because such tags can come from third-party libraries, where the consumer cannot remove them. The object is mutated correctly but the UI never updates.

const original = { [Symbol.toStringTag]: 'Goat', foo: 1 }
const state = reactive(original)
isReactive(state) // false  (expected: true)

The same applies to subclasses of Map/Set that expose a custom tag, which are detected as INVALID rather than COLLECTION.

Fix

When toRawType does not match one of the known built-in types, fall back to prototype-based checks (instanceof for collections/arrays, prototype comparison for plain objects) to determine the correct TargetType. The fast path for normal objects/arrays/collections is unchanged, so there is no overhead for the common case.

Tests

Added cases in reactive.spec.ts covering a plain object with a custom Symbol.toStringTag, and Map/Array subclasses that define a tag. They fail on main and pass with this change.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reactivity handling to ensure consistent detection of Map, Set, Array, and plain objects.

Review Change Stack

A custom `Symbol.toStringTag` changes the value returned by `toRawType`,
which previously caused `getTargetType` to treat such objects (and
subclasses of built-in collections that define a tag) as INVALID, so
they were never wrapped in a reactive proxy. Fall back to prototype
checks for these cases so they are correctly detected as a common
object or collection.

close vuejs#10483
Copilot AI review requested due to automatic review settings May 22, 2026 02:52
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54241742-95de-4f6b-8a7c-a9a94ab5f60f

📥 Commits

Reviewing files that changed from the base of the PR and between bbdf86d and c692316.

📒 Files selected for processing (2)
  • packages/reactivity/__tests__/reactive.spec.ts
  • packages/reactivity/src/reactive.ts

📝 Walkthrough

Walkthrough

Fixed type detection in reactive() by refactoring targetTypeMap to accept the full target value and apply instanceof/prototype fallback checks, allowing correct reactivity for objects with custom Symbol.toStringTag. Added tests confirming reactive behavior for such objects and collections.

Changes

Symbol.toStringTag Detection Fix

Layer / File(s) Summary
Target type detection with Symbol.toStringTag fallback
packages/reactivity/src/reactive.ts
targetTypeMap signature changed to accept the full Target value instead of a precomputed raw type string. Implementation now calls toRawType(value) internally and applies instanceof checks and prototype-based isPlainObject helper as fallback to correctly identify built-in collections (Map, Set, WeakMap, WeakSet) and Array/plain objects even when custom Symbol.toStringTag would mask the actual type. getTargetType updated to pass the full value to targetTypeMap.
Tests for reactive behavior with Symbol.toStringTag
packages/reactivity/__tests__/reactive.spec.ts
Added two test cases verifying reactive() correctly observes objects with custom Symbol.toStringTag and collection subtypes (Map, Array) with custom Symbol.toStringTag. Tests confirm reactive status and that effects respond to mutations (property access, Map.has with set, and Array.length with push).

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

ready to merge, scope: reactivity

Poem

A rabbit hops through type detection's maze,
Where Symbol.toStringTag once caused a daze,
Now fallback checks with instanceof sight,
Make proxies reactive—all's working right! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing reactivity for objects with custom Symbol.toStringTag, referenced to issue #10483.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #10483 by adding fallback prototype-based checks to correctly detect built-in types when custom Symbol.toStringTag confuses toRawType(), and includes test cases verifying the fix works.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the Symbol.toStringTag detection issue: test cases covering the fix scenario and implementation changes to the type detection logic in reactive.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

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 reactivity edge case where reactive() / ref() fail to proxy values that define a custom Symbol.toStringTag, causing toRawType()-based target classification to mis-detect the value as INVALID.

Changes:

  • Update target-type detection to fall back to prototype/instanceof checks when toRawType() returns an unknown type (custom Symbol.toStringTag).
  • Add tests covering a plain object with a custom Symbol.toStringTag, plus Map/Array subclasses with custom tags.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/reactivity/src/reactive.ts Adds fallback target-type detection when toRawType() is affected by custom Symbol.toStringTag.
packages/reactivity/tests/reactive.spec.ts Adds regression tests for custom Symbol.toStringTag on objects and collection/array subtypes.

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

Comment on lines +64 to +66
if (value instanceof Array || isPlainObject(value)) {
return TargetType.COMMON
}
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 26, 2026

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14874
npm i https://pkg.pr.new/@vue/compiler-core@14874
yarn add https://pkg.pr.new/@vue/compiler-core@14874.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14874
npm i https://pkg.pr.new/@vue/compiler-dom@14874
yarn add https://pkg.pr.new/@vue/compiler-dom@14874.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14874
npm i https://pkg.pr.new/@vue/compiler-sfc@14874
yarn add https://pkg.pr.new/@vue/compiler-sfc@14874.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14874
npm i https://pkg.pr.new/@vue/compiler-ssr@14874
yarn add https://pkg.pr.new/@vue/compiler-ssr@14874.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14874
npm i https://pkg.pr.new/@vue/reactivity@14874
yarn add https://pkg.pr.new/@vue/reactivity@14874.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14874
npm i https://pkg.pr.new/@vue/runtime-core@14874
yarn add https://pkg.pr.new/@vue/runtime-core@14874.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14874
npm i https://pkg.pr.new/@vue/runtime-dom@14874
yarn add https://pkg.pr.new/@vue/runtime-dom@14874.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14874
npm i https://pkg.pr.new/@vue/server-renderer@14874
yarn add https://pkg.pr.new/@vue/server-renderer@14874.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14874
npm i https://pkg.pr.new/@vue/shared@14874
yarn add https://pkg.pr.new/@vue/shared@14874.tgz

vue

pnpm add https://pkg.pr.new/vue@14874
npm i https://pkg.pr.new/vue@14874
yarn add https://pkg.pr.new/vue@14874.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14874
npm i https://pkg.pr.new/@vue/compat@14874
yarn add https://pkg.pr.new/@vue/compat@14874.tgz

commit: c692316

) {
return TargetType.COLLECTION
}
if (value instanceof Array || isPlainObject(value)) {
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.

use isArray instead.

@edison1105 edison1105 added 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. wait changes labels May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. wait changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symbol.toStringTag stops reactive() and ref() from being reactive.

3 participants