Please stop using reactive() in Vue 3 Composition API

Created by human

Intro

When Vue 3 introduced the Composition API, I was really excited about this new way of building components in my favorite framework. And then I was even more excited when script setup came out. I love how reactivity works in Vue 3, and in my opinion, it's the simplest and most elegant approach out of all major frameworks. But there is this one thing that we see very often: "Should I use ref or reactive?", "When should I use ref or reactive?", etc. These questions come from people with varying levels of experience, and there are some battles over the internet where people are trying to create "rules of thumb" for which scenario you should use ref() or reactive(). I've seen some statements like "ref for primitives, reactive for objects and arrays," and let's put it mildly, I don't agree 😀. Why overcomplicate something that should be so damn easy? It's easy.

Just always use ref()

Why? Because ref() can do everything that reactive() can, but reactive() can't do anything that ref() can. So the first question is, why would you mix these different reactivity functions?

<script setup lang="ts">
import { ref, reactive } from 'vue';

const searchPhrase = ref('')

const searchOptions = ref({
    matchCaseSensivity: false,
    includeIgnoredItems: false
})

const showAdvancedSearch = ref(false)
</script>

then

<script setup lang="ts">
import { ref, reactive } from 'vue';

const searchPhrase = ref('')

const searchOptions = reactive({
    matchCaseSensivity: false,
    includeIgnoredItems: false
})

const showAdvancedSearch = ref(false)
</script>

?

If you create artificial rules like "ref for primitives, reactive for objects and arrays," you introduce unnecessary mental overhead by having to make that decision every time. However, you might ask, "If I can use ref for everything, why does reactive exist?" That's a good question. When using ref(), you have to access the value through the .value property.

<script setup lang="ts">
import {ref} from 'vue';

const searchPhrase = ref('')

function clearSearchPhrase() {
    searchPhrase.value = ''
}
</script>

same with objects and arrays:

<script setup lang="ts">
import {ref} from 'vue';

const newItemName = ref('')
const items = ref([])

function addItem() {
    items.value.push(newItemName.value)
}
</script>

And if you would like to use reactive():

<script setup lang="ts">
import {ref, reactive} from 'vue';

const newItemName = ref('')
const items = reactive([])

function addItem() {
    items.push(newItemName.value)
}
</script>

With Reactive, you don't have to use '.value' to access the value of a reactive variable. Someone might say, "Oh, so it's better than ref! I don't want to add .value every time." I can sort of understand that, but take a look at the example above one more time. Can you see the problem? Sometimes it's ref(), sometimes it's reactive(). Sometimes it's '.value', sometimes not. In large codebases, it can be confusing.

I think the necessity of using .value makes you more aware of when you are using reactive variables or not. It's easy to search the codebase. Trust me, I saw a lot of bugs produced by forgotten '.value' or '.value' being present where it should be because someone used reactive().

Keeping it short: ref is more universal, and you can create the whole project with ref() only to make your code consistent. When you decide to use reactive(), you will also have to use ref(), and your codebase will be mixed with different approaches, which can cause a lot of bugs.

Another limitation of Reactive is the fact that it can't replace the entire object, while Ref can.

let state = reactive({ count: 0 })

// the above reference ({ count: 0 }) is no longer being tracked
// (reactivity connection is lost!)
state = reactive({ count: 1 })

If you are still not convinced, let's check if maybe the Vue docs are saying something about the preferred way.

Oh yes!, the official docs statement is:

"In Composition API, the recommended way to declare reactive state is using the ref() function"

And one last thing: I understand that there might be super rare edge cases, especially for library authors, where reactive might be a better choice. If you think that, in a particular scenario, reactive is the better option and you are really confident about it, then go for it! My point is that ref should be your default choice, and if somehow you end up with this super rare edge case, you can go with reactive`. Let's make it easy and not overthink this, making it easier for other people working on the same project. 🙏🏻

© Copyright 2023 Michał Kuncio