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
@netlify
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

@coderabbitai
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

@coderabbitai
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`
@coderabbitai
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
@github-project-automation github-project-automation bot moved this to 🆕 Triaging in Pinia Roadmap Nov 1, 2025
@posva posva moved this from 🆕 Triaging to 📋 Backlog in Pinia Roadmap Nov 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 📋 Backlog

Development

Successfully merging this pull request may close these issues.

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

1 participant