Skip to content

Commit 640b16b

Browse files
posvaWaradu
andauthored
fix(devtools): make settable getters (computed) editable (#3151)
* fix: make settable getters (computed) editable * refactor(devtools): detect writable computed at runtime Replace the _editableComputed bookkeeping set with a runtime isReadonly() check on the raw computed ref. Also drop the dead edit handlers for the root inspector and component inspector, whose getters entries are not editable. * chore: computed with setter in playground --------- Co-authored-by: Waradu <waradu@outlook.com>
1 parent d715bda commit 640b16b

4 files changed

Lines changed: 24 additions & 6 deletions

File tree

‎packages/pinia/src/devtools/formatting.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { MutationType, StoreGeneric } from '../types'
22
import { DebuggerEvent } from 'vue'
33
import { Pinia } from '../rootStore'
4-
import { isPinia } from './utils'
4+
import { isPinia, isWritableComputed } from './utils'
55

66
// types from devtools-api
77
interface StateBase {
@@ -153,7 +153,7 @@ export function formatStoreForInspectorState(
153153
// avoid adding empty getters
154154
if (store._getters && store._getters.length) {
155155
state.getters = store._getters.map((getterName) => ({
156-
editable: false,
156+
editable: isWritableComputed(store, getterName),
157157
key: getterName,
158158
value: store[getterName],
159159
}))

‎packages/pinia/src/devtools/plugin.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
PINIA_ROOT_ID,
2424
PINIA_ROOT_LABEL,
2525
} from './formatting'
26-
import { isPinia, toastMessage } from './utils'
26+
import { isPinia, isWritableComputed, toastMessage } from './utils'
2727

2828
// timeline can be paused when directly changing the state
2929
let isTimelineActive = true
@@ -262,10 +262,12 @@ export function registerPiniaDevtools(app: App, pinia: Pinia) {
262262
const { path } = payload
263263

264264
if (!isPinia(inspectedStore)) {
265-
// access only the state
265+
// access only the state unless it's a custom property or a
266+
// writable computed, which are set directly on the store
266267
if (
267268
path.length !== 1 ||
268-
!inspectedStore._customProperties.has(path[0]) ||
269+
(!inspectedStore._customProperties.has(path[0]) &&
270+
!isWritableComputed(inspectedStore, path[0])) ||
269271
path[0] in inspectedStore.$state
270272
) {
271273
path.unshift('$state')

‎packages/pinia/src/devtools/utils.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { isReadonly, isRef, toRaw } from 'vue'
12
import { Pinia } from '../rootStore'
3+
import { StoreGeneric } from '../types'
24

35
/**
46
* Shows a toast or console.log
@@ -24,3 +26,12 @@ export function toastMessage(
2426
export function isPinia(o: any): o is Pinia {
2527
return '_a' in o && 'install' in o
2628
}
29+
30+
/**
31+
* Detects if a store getter is a writable computed (has a setter) so it can be
32+
* edited from devtools.
33+
*/
34+
export function isWritableComputed(store: StoreGeneric, key: string): boolean {
35+
const rawProp = toRaw(store)[key]
36+
return isRef(rawProp) && !isReadonly(rawProp)
37+
}

‎packages/playground/src/stores/counterSetup.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ export const useCounter = defineStore('counter-setup', () => {
1414
const injected = inject('injected', 'fallback value')
1515
console.log('injected (should be global)', injected)
1616

17-
const double = computed(() => state.n * 2)
17+
const double = computed<number>({
18+
get: () => state.n * 2,
19+
set: (v) => {
20+
state.n = v / 2
21+
},
22+
})
1823

1924
function increment(amount = 1) {
2025
if (typeof amount !== 'number') {

0 commit comments

Comments
 (0)