Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

defineStore doesn't respect readonly ref returned from storeSetup fn #872

Closed
ycw opened this issue Dec 3, 2021 · 9 comments
Closed

defineStore doesn't respect readonly ref returned from storeSetup fn #872

ycw opened this issue Dec 3, 2021 · 9 comments

Comments

@ycw
Copy link

ycw commented Dec 3, 2021

Reproduction

Define a store

// myStore.js
import { defineStore } from 'pinia'
import { ref, readonly } from 'vue'

export default defineStore('myStore', () => {
  const x = ref(0)
  // ..   
  return { x: readonly(x) }  // << readonly
})

Then consume the store

<!-- App.vue -->
<script setup>
import useMyStore from './path/to/myStore.js'
const store = useMyStore()
</script>
<template>
  <a @click='++store.x'> {{ store.x }} </a>
  <!-- what happens if users click to mutate a readonly ref ? -->
</template>

Expected behavior

I expected that vue will show warnings in web console when the anchor is clicked

Actual behavior

when the anchor is clicked, store.x gets mutated; no warnings in web console.

Additional information

deps

"pinia": "^2.0.5",
"vue": "^3.2.16",

Thanks.

@posva
Copy link
Member

posva commented Dec 3, 2021

readonly is for reactive: https://v3.vuejs.org/api/basic-reactivity.html#readonly

@posva posva closed this as completed Dec 3, 2021
@ycw
Copy link
Author

ycw commented Dec 4, 2021

@posva my temp. workaround is using a no-setter computed ref

// myStore.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export default defineStore('myStore', () => {
  const x = ref(0)
  // ..   
  return { x: computed(() => x.value) } // << no-setter = readonly
})

and users will get a warning on mutating x as expected

<!-- App.vue -->
<script setup>
import useMyStore from './path/to/myStore.js'
const store = useMyStore()
</script>
<template>
  <button @click='++store.x'> {{ store.x }} </button>
  <!-- vue will warn when users click -->
  <!-- Write operation failed: computed value is readonly -->
</template> 

@ycw
Copy link
Author

ycw commented Dec 4, 2021

Seems like a Vue issue: https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiwgcmVhZG9ubHksIHJlYWN0aXZlIH0gZnJvbSAndnVlJ1xuXG5jb25zdCB6ID0gcmVmKDApO1xuY29uc3QgcmVhZG9ubHlaID0gcmVhZG9ubHkoeik7XG4gIGNvbnN0IHN0b3JlID0gcmVhY3RpdmUoe30pXG4gIHN0b3JlLnJlYWQgPSByZWFkb25seVpcbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIHt7IHogfX1cbiAgPGJ1dHRvbiBAY2xpY2s9Jysrc3RvcmUucmVhZCc+IGluY3IgcmVhZG9ubHlaIDwvYnV0dG9uPlxuICA8IS0tIHZ1ZSB3aWxsIHdhcm4gb24gdXNlcnMgY2xpY2sgLS0+XG4gIDwhLS0gU2V0IG9wZXJhdGlvbiBvbiBrZXkgXCJ2YWx1ZVwiIGZhaWxlZDogdGFyZ2V0IGlzIHJlYWRvbmx5LiAtLT5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSJ9

Or maybe it's intended, I will have to check

@posva thanks. but its not quite related to my original issue ( my issue is on ref itself, not 'reactive property is a ref' ), let me illustrate the issue in another way, I expected that pinia store respects readonly refs just like how vue provide/inject mechanism. ex:

Provider

<!-- App.vue -->
<script setup>
import { ref, readonly, provide } from 'vue'
import MyComp from './MyComp.vue'

const x = ref(0);
provide('x', readonly(x));  // analog : return { x: readonly(x) } in storeSetup fn 
</script>

<template>
  <MyComp></MyComp>
</template>

Then, in injectors

<!-- MyComp.vue -->
<script setup>
import { inject } from 'vue'
  
const x = inject('x');  // analog :  get `x` from store
</script>

<template>
  {{ x }}
  <button @click='++x'> incr x </button>
  <!-- vue will warn when users click -->
  <!-- Set operation on key "value" failed: target is readonly. -->
</template>

demo : https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiwgcmVhZG9ubHksIHByb3ZpZGUgfSBmcm9tICd2dWUnXG5pbXBvcnQgTXlDb21wIGZyb20gJy4vTXlDb21wLnZ1ZSdcblxuY29uc3QgeCA9IHJlZigwKTtcbnByb3ZpZGUoJ3gnLCByZWFkb25seSh4KSk7ICAvLyBwcm92aWRlIGEgcmVhZG9ubHkgcmVmXG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8TXlDb21wPjwvTXlDb21wPlxuPC90ZW1wbGF0ZT4iLCJpbXBvcnQtbWFwLmpzb24iOiJ7XG4gIFwiaW1wb3J0c1wiOiB7XG4gICAgXCJ2dWVcIjogXCJodHRwczovL3NmYy52dWVqcy5vcmcvdnVlLnJ1bnRpbWUuZXNtLWJyb3dzZXIuanNcIlxuICB9XG59IiwiTXlDb21wLnZ1ZSI6IjxzY3JpcHQgc2V0dXA+XG5pbXBvcnQgeyBpbmplY3QgfSBmcm9tICd2dWUnXG4gIFxuY29uc3QgeCA9IGluamVjdCgneCcpO1xuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAge3sgeCB9fVxuICA8YnV0dG9uIEBjbGljaz0nKyt4Jz4gaW5jciB4IDwvYnV0dG9uPlxuICA8IS0tIHZ1ZSB3aWxsIHdhcm4gd2hlbiB1c2VycyBjbGljayAtLT5cbiAgPCEtLSBTZXQgb3BlcmF0aW9uIG9uIGtleSBcInZhbHVlXCIgZmFpbGVkOiB0YXJnZXQgaXMgcmVhZG9ubHkuIC0tPlxuPC90ZW1wbGF0ZT4ifQ==

@posva
Copy link
Member

posva commented Dec 4, 2021

It’s how pinia worka

@ycw
Copy link
Author

ycw commented Dec 4, 2021

It’s how pinia worka

@posva thanks, good hint. Here's my new understanding:

When we call useMyStore(), it ultimately returns a reactive() wrapping the obj returned from storeSetup fn;

import { isReactive } from 'vue'
import useMyStore from './myStore.js'
const store = useMyStore()
isReactive(store) // true  

Since vue doesn't preserve readonly(aRef) semantic for reactive property ( value is auto unwrapped on access when it is a ref ), to prevent returned refs from mutating, we can use no-setter ComputedRef instead ( #872 (comment) )

Right?

@Anoesj
Copy link

Anoesj commented Feb 15, 2024

Don't know if this is useful to anyone, but I found this issue while trying to find out if this is possible with Pinia, and it turns out returning a readonly ref works just fine nowadays! Only the typings are wrong when using the readonly ref in a setup block using storeToRefs.

So (using an updated version of the example in the OP here):

// myStore.js
import { defineStore } from 'pinia'
import { ref, readonly } from 'vue'

export default defineStore('myStore', () => {
  const x = ref(0)
  // ..   
  return { x: readonly(x) }  // << readonly
})
<!-- App.vue -->
<script setup>
import useMyStore from './path/to/myStore.js'
const { x } = storeToRefs(useMyStore())
// This ^ is typed wrong! Is now Ref<boolean>, should be Readonly<Ref<boolean>>
</script>
<template>
  <a @click='++x'> {{ x }} </a>
  <!--
    When user triggers mutating of a readonly ref, Vue warns in the console:
    "Set operation on key "value" failed: target is readonly."
  -->
</template>

Related: #2574

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

No branches or pull requests

3 participants