Must-Know Ref vs Reactive Differences in Vue 3 Composition API
By: Raja Tamil
Tags: Programming, Vue

Ref() and Reactive() are the new ways of creating reactive property introduced in Composition API Vue 3.

They are wrapper objects that can be initialized with inner values and assigned to variables.

In Vue 3, we need to import the desired package first before using it in the component.

Ref()

We could create a variable as we normally do inside a setup function and add it to the returned object.

Then render it in the template.

This will work but there will be no reactivity.

<template>

{{count}}

</template>

<script>

export default {

setup() {

let count = 0;

return {

count,

};

},

};

</script>

One of the ways we can create a property without losing its reactivity is by using ref().

The ref() object takes an inner value and returns a reactive and mutable object.

It’s great for primitive type single variables such as String, Boolean, Number, etc.

It has a single property called .value that points to the inner value that’s how we can get and set value to the property.

Import the ref package at the top.

import { ref } from 'vue';

The count variable holds a ref() object with the inner value 0.

let count = ref(0);

The ref() object will have a single property called value that points to the inner value which is 0 in this case.

To get or set a value to the count variable, we can unwrap the value of the name variable using its property .value.

console.log(count.value); // 0 get

count.value = 12 // 12 set

Then we can render the count variable by returning it to the setup() function like below.

As you’ve noticed in the code below, the count property is rendered in the template without using .value property on it.

This is because when a ref object is added to the returned object from the setup function, it automatically unwraps the inner value when we use it in the template.

<template>

{{count}}

</template> <script>

import { ref } from "vue";

export default {

setup() {

let count = ref(0);

return {

count,

};

},

};

</script>

I use semantic ui CSS framework to style the UI.

To check the reactivity on the count property, attach a click event to a button element.

Then add a number to the count property incremented by 1.

<template>

<div

style="

display: flex;

justify-content: center;

align-items: center;

height: 100vh;"

>

<button class="ui button red" @click="countNumber">Count</button>

<div class="ui label big">{{ count }}</div>

</div>

</template>

<script>

import { ref } from "vue";

export default {

setup() {

let count = ref(0); function countNumber() {

count.value++;

} return {

count,

countNumber

};

},

};

</script>

And the reactivity works as expected.

Reactive()

The reactive() is also a wrapper object that takes an object and returns a reactive proxy of the original object.

It‘s great for dictionary-structured types such as JS Object.

Import the reactive package at the top.

import { reactive } from 'vue';

This is very similar to the ref object but the inner value should be dictionary-structured data like JS object instead of a single value.

let count = reactive({val: 0});

Using a proxy object, we can access inner object properties like we normally do.

console.log(count.val);

To make this object reactive, all we have to do is to increment the val property by 1 inside the button click event callback function.

The rest is the same.

<script>

import { reactive } from "vue";

export default {

setup() {

let count = reactive({val:0}); function countNumber() {

count.val++;

} return {

count,

countNumber,

};

},

};

</script>

Which then gives the same result.

Ok, What if I use reactive for a single inner value?

The variable will lose its reactivity.

When you create a variable using a reactive wrapper object with a single value, there will a warning in the browser console saying:

value cannot be made reactive: 0

On the flip side, I can literally use the ref() instead of reactive() for JavaScript objects and it’ll work fine but under the hood, it’ll use reactive wrapper object to keep the reactivity of the inner data.

<script>

import { ref } from "vue";

export default {

setup() {

let count = ref({ val: 0 }); function countNumber() {

count.value.val++;

} return {

count,

countNumber,

};

},

};

</script>

You may wonder…well I could use a ref object for all of my variable creation as they can work with single value types or complex data types.

That’ll work but it would be tedious to use .value property everywhere when getting or setting variable data.

To limit that, I would recommend using ref for only single value types such as string, etc.

Using reactive() feels right when dealing with complex objects such as objects, arrays, etc.

One thing I noticed is that an array type of variable won’t lose its reactivity regardless of using ref or reactive as it’s technically an object in JavaScript.

Working With Async Data

Using re() method, we can do that pretty quickly.

I use setTimeout() to mock the API call.

<template>

<div

style="

display: flex;

justify-content: center;

align-items: center;

height: 100vh;

"

>

<div class="ui label big">

ID: {{ user.id }} <br />

Name: {{ user.name }}

</div>

</div>

</template> <script>

import { ref } from "vue";

export default {

setup() {

let user = ref({ id: 0, name: "Joe" }); setTimeout(() => {

user.value = {

id: 20,

name: "Raja",

};

}, 2000); return {

user,

};

},

};

</script>

As you can see in the above example, the new data can be rendered in the DOM after 2 seconds and pretty straight forward.

Let's see how to do that using the reactive() method.

<template>

<div

style="

display: flex;

justify-content: center;

align-items: center;

height: 100vh;

"

>

<div class="ui label big">

ID: {{ user.id }} <br />

Name: {{ user.name }}

</div>

</div>

</template> <script>

import { reactive } from "vue";

export default {

setup() {

let user = reactive({ id: 0, name: "Joe" }); setTimeout(() => {

user = {

id: 20,

name: "Raja",

};

}, 2000); return {

user,

};

},

};

</script>

The above code loses reactivity on the user object as we can not directly change the entire object. Instead, we can only change the properties of the user object.

As you can see in the below code, I declare the user object separately then add it to an inner object of reactive() method using user property.

Then I can easily assign new data to that property inside the setTimeout() function which will change the data after 2 seconds.

<template>

<div

style="

display: flex;

justify-content: center;

align-items: center;

height: 100vh;

"

>

<div class="ui label big">

ID: {{ state.user.id }} <br />

Name: {{ state.user.name }}

</div>

</div>

</template> <script>

import { reactive } from "vue";

export default {

setup() {

let user = { id: 0, name: "Joe" }; let state = reactive({ user }); setTimeout(() => {

state.user = {

id: 20,

name: "Raja",

};

}, 2000); return {

state,

};

},

};

</script>

Summary

I hope this article clears up some of the key differences between ref and reactive wrapper objects and how to use them to create variables without losing their reactivity.

I also showed you when to choose one over the other.

If you’ve any questions, feel free to comment below and I’ll get back to you as soon as I can.

Happy Coding.