
If you have used the Composition API in Vue 3, you have probably come across the ref and reactive functions. These are used to create reactive data in Vue 3. reactive only works with objects, while ref can be used with any type of data.
Eduardo San Martin Morote
April 8, 2024
If you have used the Composition API in Vue 3, you have probably come across the ref and reactive functions. These are used to create reactive data in Vue 3. reactive only works with objects, while ref can be used with any type of data. Also, you cannot reassign a new value to a reactive object, but you can with a ref:
const user = reactive({
id: 1,
name: 'John Doe',
avatar: '<https://example.com/avatar.jpg>',
})
user = {
/* ... */
} // TypeError
const user = ref({
id: 1,
name: 'John Doe',
avatar: '<https://example.com/avatar.jpg>',
})
user.value = {
/* ... */
} // Works
But really, the main difference people see is in the usage:
ref, you access the value with .value, which might feel a bit annoying at firstreactive, you can access the properties directlyThis is very noticeable when working with collections like arrays, Set, Maps, etc:
const users = ref([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
])
// 👇
users.value.push({ id: 3, name: 'Foo Bar' })
vs
const users = reactive([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
])
// ✨
users.push({ id: 3, name: 'Foo Bar' })
The truth is we shouldn't be picking one over the other based on this but based on how the data is modified. If the data is going to be replaced as a whole, it should be a ref (or even a [shallowRef](https://vuejs.org/api/reactivity-advanced.html#shallowref) for performance reasons).
Personally, I really like using reactive for JS collections like Sets and Maps because they all include a convenient .clear() method to reset them!
So what does this have to do with stores? Well, when creating Setup Stores, you can choose to use ref or reactive to create state but reactive can be problematic. Let me show you why.
export const useUsersStore = defineStore('users', () => {
const userList = reactive<User[]>([])
function addUser(user: User) {
userList.push(user)
}
return { userList, addUser }
})
We are using userList to conveniently store a list of users. Any modification within the store works perfectly but if we try to replace the userList outside the store, something weird will happen...
const usersStore = useUsersStore()
// naively trying to replace the userList
usersStore.userList = []
userList will no longer be reactive 🥲. You can try this yourself here.
The reason this doesn't work is because the userList within the store is no longer the same as the userList outside the store. So addUser() is modifying a reactive array but we are not using it with usersStore.userList. The reason behind this is very simple! Pinia stores are wrapped with reactive() to remove the need of writing .value everywhere.
In fact, we can even see the same issue with just Vue!
const userList = reactive([])
function addUser(user) {
// this only modifies the reactive array
userList.push(user)
}
const usersStore = reactive({ userList })
function reset() {
// this replaces the usersStore.userList with a new array
usersStore.userList = []
}
This is why I recommend using ref (and shallowRef) in stores as much as possible. It's the most flexible choice. I still find myself using reactive and shallowReactive for Sets and Maps.
In short, use reactive (and shallowReactive) only if you don't replace the data as a whole. Otherwise, use ref (and shallowRef).
Get a free lesson delivered to your inbox, just click on the button below.