Ref vs Reactive in Stores

Ref vs Reactive in Stores

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

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:

  • with ref, you access the value with .value, which might feel a bit annoying at first
  • with reactive, you can access the properties directly

This 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).

The Mastering Pinia Course is Here!

Get a free lesson delivered to your inbox, just click on the button below.

Buy Now