Skip to content

Conversation

doubledare704
Copy link

@doubledare704 doubledare704 commented Sep 14, 2025

  • Add triggerRef import from Vue
  • Implement isShallowRef utility function to detect shallowRef using __v_isShallow
  • Modify method to detect shallowRef targets and trigger reactivity after patching
  • Add comprehensive tests for shallowRef reactivity scenarios
  • Ensure compatibility with existing patch behavior

close #2861

Summary by CodeRabbit

  • Bug Fixes

    • Restored reliable reactivity for shallowRef state in setup stores when using $patch. Nested, partial, multi-field, and direct-assignment updates now consistently notify watchers and computed values without changing public APIs.
  • Tests

    • Added comprehensive tests covering shallowRef behavior with both object and function patch syntax, nested updates, partial updates, multi-ref patches, direct assignments, and reactivity observation to prevent regressions.

- Add triggerRef import from Vue
- Implement isShallowRef utility function to detect shallowRef using __v_isShallow
- Modify  method to detect shallowRef targets and trigger reactivity after patching
- Add comprehensive tests for shallowRef reactivity scenarios
- Ensure compatibility with existing patch behavior

close vuejs#2861
Copy link

netlify bot commented Sep 14, 2025

Deploy Preview for pinia-official canceled.

Name Link
🔨 Latest commit a66b124
🔍 Latest deploy log https://app.netlify.com/projects/pinia-official/deploys/68c7b5bec4e0fa0008322578

Copy link

coderabbitai bot commented Sep 14, 2025

Walkthrough

Adds tests covering shallowRef reactivity with store.$patch and updates setup-store $patch logic to detect patched shallowRefs (via toRaw) and call triggerRef for shallowRef values that received plain-object merges so Vue reactivity is notified.

Changes

Cohort / File(s) Summary
ShallowRef reactivity tests for $patch
packages/pinia/__tests__/store.patch.spec.ts
New test suite: adds tests using shallowRef, computed, watchEffect, and nextTick to verify $patch behavior for object- and function-syntax patches, nested/partial patches, direct assignment, and multi-ref patches.
Store patch logic & shallowRef triggering
packages/pinia/src/store.ts
Imports isShallow/triggerRef; adds isShallowRef helper; uses Object.prototype.hasOwnProperty.call in merge checks; uses toRaw(store) after merging to find shallowRefs whose patched values are plain objects and calls triggerRef to notify Vue reactivity.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor C as Component / Watcher
  participant S as Setup Store
  participant P as store.$patch
  participant M as mergeReactiveObjects
  participant Raw as toRaw(store)
  participant R as shallowRef(s)
  participant V as Vue Reactivity

  C->>S: depend on shallowRef-based state (computed/watch)
  S->>P: call $patch(partialState)
  P->>M: merge partial into reactive store state
  M-->>P: merge complete
  P->>Raw: inspect toRaw(store) for shallowRefs affected
  alt patched shallowRef found (plain-object update)
    P->>R: call triggerRef() on each affected shallowRef
    R->>V: notify dependents
    V-->>C: computed/watch rerun
  else no shallowRef affected
    P-->>S: patch completes (no manual trigger)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nudge the shallow things with gentle paws,
Merge crumbs fall soft without applause.
triggerRef taps — a tiny, honest drum,
Watchers wake and computations hum.
Hops of tests confirm the sound: reactivity come! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 accurately and concisely summarizes the PR's primary change: it states the fix is to trigger reactivity for shallowRef when using object-syntax $patch, which matches the code and tests added in this changeset.
Linked Issues Check ✅ Passed The PR implements shallowRef detection and calls triggerRef after object-syntax $patch, uses toRaw to inspect the raw store without unwrapping refs, and adds comprehensive tests covering object-syntax, function-syntax, direct assignment, nested updates, and multi-ref patches, which directly address the coding requirements of issue #2861.
Out of Scope Changes Check ✅ Passed All changes are focused on fixing shallowRef $patch reactivity and necessary support (shallowRef helper, triggerRef calls, toRaw usage and tests); the hasOwnProperty defensive change and import adjustments are minor, related refactors and do not introduce unrelated functionality.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 819f21c and a66b124.

📒 Files selected for processing (1)
  • packages/pinia/src/store.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/pinia/src/store.ts

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/pinia/src/store.ts (2)

96-102: Avoid direct .hasOwnProperty; use the safe prototype call.

target can be a proxy or an object without a prototype. Prefer Object.prototype.hasOwnProperty.call(target, key) to avoid edge cases. The extra guard here is also redundant because isPlainObject(targetValue) already implies “own” in this context.

Apply this diff:

-      target.hasOwnProperty(key) &&
+      Object.prototype.hasOwnProperty.call(target, key) &&

151-158: isShallowRef relies on a private flag; add a type and a defensive check.

Using __v_isShallow is internal API. It’s fine pragmatically, but please (a) narrow the return type to ShallowRef<any> and (b) guard with strict equality to avoid truthy pitfalls.

You can enhance it like this (type-only import outside this hunk):

// import type { ShallowRef } from 'vue'
function isShallowRef(value: any): value is ShallowRef<any> {
  return isRef(value) && (value as any).__v_isShallow === true
}
packages/pinia/__tests__/store.patch.spec.ts (1)

219-401: Great coverage for shallowRef + object‑syntax $patch.

Happy path, nested objects, function syntax baseline, direct assignment baseline, partial updates, and multi‑ref patches are all exercised.

Consider adding:

  • A negative test to ensure watchers don’t fire on unrelated keys in the same $patch.
  • A case where the shallowRef is patched with a non‑plain object (e.g., Date or Array) to assert intended behavior (trigger or not), documenting expectations.

Example to append:

it('does not trigger when patching unrelated key', async () => {
  const store = useShallowRefStore()
  const spy = vi.fn()
  const stop = watchEffect(() => { void store.counter.count; spy() })
  spy.mockClear()
  store.$patch({ nestedCounter: { simple: 123 } })
  await nextTick()
  expect(spy).not.toHaveBeenCalled()
  stop()
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57bec95 and 3cf2466.

📒 Files selected for processing (2)
  • packages/pinia/__tests__/store.patch.spec.ts (2 hunks)
  • packages/pinia/src/store.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/pinia/__tests__/store.patch.spec.ts (1)
packages/pinia/src/store.ts (1)
  • defineStore (878-973)
packages/pinia/src/store.ts (1)
packages/pinia/src/types.ts (1)
  • isPlainObject (19-29)
🔇 Additional comments (2)
packages/pinia/src/store.ts (1)

23-24: Import triggerRef: LGTM.

Needed to manually notify dependents of shallow refs after object‑syntax patches.

packages/pinia/__tests__/store.patch.spec.ts (1)

1-2: Test imports: LGTM.

Covers the added shallowRef scenarios with computed, watchEffect, and nextTick.

@doubledare704
Copy link
Author

@coderabbitai generate docstrings

Copy link

coderabbitai bot commented Sep 14, 2025

✅ Actions performed

Docstrings generation started.

coderabbitai bot added a commit that referenced this pull request Sep 14, 2025
Docstrings generation was requested by @doubledare704.

* #3041 (comment)

The following files were modified:

* `packages/pinia/src/store.ts`
Copy link

coderabbitai bot commented Sep 14, 2025

Note

Generated docstrings for this pull request at #3043

- Replace setupStoreRef with toRaw(store) to eliminate coupling and state drift
- Use Object.prototype.hasOwnProperty.call() for safer property checking
- Improve type safety with Ref[] instead of any[]
- Remove unnecessary setupStoreRef assignment and variable declaration
- Maintain same functionality with more robust implementation
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/pinia/src/store.ts (1)

96-103: Use safe hasOwnProperty to avoid prototype edge cases.

Direct calls can throw for objects with null prototypes and are weaker against prototype pollution scenarios.

-      target.hasOwnProperty(key) &&
+      Object.prototype.hasOwnProperty.call(target, key) &&

Optional consistency tweak for the guard above (outside the changed hunk but same function):

-    if (!patchToApply.hasOwnProperty(key)) continue
+    if (!Object.prototype.hasOwnProperty.call(patchToApply, key)) continue
🧹 Nitpick comments (1)
packages/pinia/src/store.ts (1)

528-528: Nit: remove leftover “no‑op” comment.

It’s self‑evident from the code and can be dropped to reduce noise.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cf2466 and 819f21c.

📒 Files selected for processing (1)
  • packages/pinia/src/store.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/pinia/src/store.ts (1)
packages/pinia/src/types.ts (1)
  • isPlainObject (19-29)
🔇 Additional comments (1)
packages/pinia/src/store.ts (1)

323-339: ShallowRef reactivity trigger after object‑syntax patch: LGTM.

Good use of toRaw(store) and own‑key filtering; triggering once per patched shallowRef is correct.

- Replace internal __v_isShallow with public isShallow API for Vue version compatibility
- Fix hasOwnProperty usage in mergeReactiveObjects to prevent prototype pollution
- Remove unnecessary no-op comment for cleaner code
- Maintain all existing functionality while improving code quality
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.

Mutating a shallowRef object using $patch doesn't trigger reactivity
1 participant