DEV Community

Cover image for Please stop using reactive() in Vue 3 Composition API 🙏🏻
Michał Kuncio
Michał Kuncio

Posted on • Originally published at michalkuncio.com

Please stop using reactive() in Vue 3 Composition API 🙏🏻

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

?

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 })
Enter fullscreen mode Exit fullscreen mode

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. 🙏🏻


If you like my posts, please follow me on Twitter:
https://twitter.com/michalkuncio

Top comments (6)

Collapse
 
alaindet profile image
Alain D'Ettorre

I've always thought reactive was just a bad API overall: it's syntactic sugar upon ref() and has a non-descriptive name. I think refs() would have been a better and shorter name and would have served the purpose of clearifying that refs() depends on ref()

Collapse
 
michalkuncio profile image
Michał Kuncio

Yes, I agree, although I may not be aware of some reasons behind it.

Collapse
 
shafqatsha profile image
Shafqat M Shah

Thanks, I had the same question and same answer. "ref for primitive and reactive for non-primitive". you got me ;). ref is the final answer.

Collapse
 
michalkuncio profile image
Michał Kuncio

That's great to hear!

Collapse
 
leamsigc profile image
Ismael Garcia

Really good advice!!!

Collapse
 
michalkuncio profile image
Michał Kuncio

I'm glad that you liked it!