Ref vs. Reactive — Which is Best?
Tags: vue, productivity, javascript
Published: 2023-02-22 15:41:15

---
title: Ref vs. Reactive — Which is Best?
published: true
description: "This has been a question on the mind of every Vue dev since the Composition API was first released: What’s the difference between `ref` and `reactive`, and which one is better?"
tags: vue, productivity, javascript
canonical_url: https://michaelnthiessen.com/ref-vs-reactive/
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-02-22 15:32 +0000
---


_A huge thanks to everyone who looked at early drafts of this: especially Austin Gil for challenging my arguments, Eduardo San Martin Morote, Daniel Roe, Markus Oberlehner, and Matt Maribojoc_

This has been a question on the mind of every Vue dev since the Composition API was first released:

**What’s the difference between `ref` and `reactive`, and which one is better?**

My extremely short answer is this: default to using `ref` wherever you can.

Now, that’s not a very satisfying answer, so let’s take some more time to go through all of the reasons why I think `ref` is better than `reactive` — and why you shouldn’t believe me.

Here’s what our journey will look like, in roughly three acts:

- **[Act 1: The differences between `ref` and `reactive`](#act-1-the-differences-between-ref-and-reactive)** — First, we’ll go through all of the ways that `ref` and `reactive` are different. I’ll try to avoid giving any judgment at this point so you can see all the ways they’re different.
- **[Act 2:  The `ref` vs `reactive` debate](https://michaelnthiessen.com/ref-vs-reactive#act-2-the-ref-vs-reactive-debate)** — Next, I’ll lay out the main arguments for `ref` and for `reactive`, giving the pros and cons of each. *At this point you will be able to make your own well-informed decision.*
- **[Act 3: Why I prefer `ref`](https://michaelnthiessen.com/ref-vs-reactive#act-3-why-i-prefer-ref)** — I end the article stating my own opinion and sharing my own strategy. I also share what others in the Vue community think about this whole debate, since one person’s opinion only counts for so much (ie. very little).

More than just a discussion of “ref vs. reactive”, I hope that as we explore this question you’ll come away with additional insights that will improve your understanding of the Composition API!

Also, this is a long article, so if you don’t have time to read it all now, definitely set this aside and come back to it — you’ll thank me later!

## tl;dr — My highly opinionated and simple strategy

But first, a quick summary of my strategy for choosing.

With roughly increasing levels of complexity:

1. Start with using `ref` everywhere
2. Group related things with `reactive` if you need to
3. Take related state — and the methods that operate on them — and create a composable for them (or better yet, create a store in Pinia)
4. Use `reactive` where you want to “reactify” some other JS object like a `Map` or `Set`.
5. Use `shallowRef` and other more use-case-specific ref functions for any necessary edge cases.

## Act 1: The differences between ref and reactive

First, I want to take some time to specifically discuss how `ref` and `reactive` are different, and their different uses in general.

I’ve tried to be exhaustive in this list. Of course, I’ve probably missed some things — please let me know if you know of something I haven’t included!

With that out of the way, let’s look at some differences between these two tools we’ve been given.

### Dealing with .value

The most obvious distinction between `ref` and `reactive` is that while `reactive` quietly adds some magic to an object, `ref` requires you to use the `value` property:

```js
const reactiveObj = reactive({ hello: 'world' });
reactiveObj.hello = 'new world';

const refString = ref('world');
refString.value = 'new world';
```

When you see `something.value` and you’re already familiar with how `ref` works, it’s easy to understand at a glance that this is a reactive value. With a reactive object, this is not *necessarily* as clear.

```js
// Is this going to update reactively?
// It's impossible to know just from looking at this line
someObject.property = 'New Value';

// Ah, this is likely a ref
someRef.value = 'New Value';
```

But here are some caveats:

1. If you *don’t* already understand how `ref` works, seeing `.value` means nothing to you. In fact, for someone new to the Composition API, `reactive` is a much more intuitive API.
2. It’s possible that non-reactive objects have a `value` property. But because this clashes with the `ref` API I would consider this an anti-pattern, whether or not you like using `ref`.

This is actually the difference that this entire debate hinges on — but we’ll get to that later.

The important thing to remember for now is that using either `ref` or `reactive` requires us to access a property.

### Tooling and Syntax Sugar

The main disadvantage of `ref` here is that we have to write out these `.value` accessors all over the place. It can get quite tedious!

Fortunately, we have some extra tools that can help us mitigate this problem:

1. Template unwrapping
2. Watcher unwrapping
3. Volar

In many places Vue does this unwrapping of the `ref` for us, so we don’t even need to add `.value`. In the template we simply use the name of the `ref`:

```html
<template>
  <div>{{ myRef }}</div>
</template>
```

```html
<script setup>
const myRef = ref('Please put this on the screen');
</script>
```

And when using a watcher we specify the dependencies we want to be tracked, we can use a `ref` directly:

```js
import { watch, ref } from 'vue';

const myRef = ref('This might change!');

// Vue automatically unwraps this ref for us
watch(myRef, (newValue) => console.log(newValue));
```

Lastly, the Volar VS Code extension will autocomplete refs for us, adding in that `.value` wherever it’s needed. You can enable this in the settings under `Volar: Auto Complete Refs`:

![Enabling .value auto-complete in Volar](./volar.png)

You can also enable it through the JSON settings:

```json
"volar.autoCompleteRefs": true
```

It is disabled by default to keep the CPU usage down.

### ref uses reactive internally

Here’s something interesting you may not have realized.

When you use an object (including Arrays, Dates, etc.) with `ref`, it’s actually calling `reactive` under the hood.

Anything that isn’t an object — a string, a number, a boolean value — and `ref` uses its own logic.

You can see it working in these two lines:

1. [Line 1](https://github.com/vuejs/core/blob/ce064a172b3b5bcb096b4984fe0c1a54177ac9c0/packages/reactivity/src/ref.ts#L113): Creating a `ref` involves calling `toReactive` to get the internal value
2. [Line 2](https://github.com/vuejs/core/blob/ce064a172b3b5bcb096b4984fe0c1a54177ac9c0/packages/reactivity/src/reactive.ts#L251-L252): `toReactive` only calls `reactive` if the passed value is an object

```json
// Ref uses reactive for non-primitive values
// These two statements are approximately the same
ref({}) ~= ref(reactive({}))
```

### Reassigning Values

Vue developers for years have been tripped up by how reactivity works when reassigning values, especially with objects and arrays:

```js
// You got a new array, awesome!
// ...but does it properly update your app?
myReactiveArray = [1, 2, 3];
```

This was a big issue with Vue 2 because of how the reactivity system worked. Vue 3 has *mostly* solved this, but we’re still dealing with this issue when it comes to `reactive` versus `ref`.

You see, `reactive` values cannot be reassigned how you’d expect:

```js
const myReactiveArray = reactive([1, 2, 3]);

watchEffect(() => console.log(myReactiveArray));
// "[1, 2, 3]"

myReactiveArray = [4, 5, 6];
// The watcher never fires
// We've replaced it with an entirely new, non-reactive object
```

This is because the reference to the previous object is overwritten by the reference to the new object. We don’t keep that reference around anywhere.

**The proxy-based reactivity system only works when we access properties on an object.**

I’m going to repeat that because it’s such an important piece of the reactivity puzzle.

**Reassigning values will not trigger the reactivity system. You *must* modify a property on an existing object.**

This also applies to refs, but this is made a little easier because of the standard `.value` property that each `ref` has:

```js
const myReactiveArray = ref([1, 2, 3]);

watchEffect(() => console.log(myReactiveArray.value));
// "[1, 2, 3]"

myReactiveArray.value = [4, 5, 6];
// "[4, 5, 6]"
```

Both `ref` and `reactive` are required to access a property to keep things reactive, so no real difference there.

But, where this is the expected way of using a `ref`, it’s not how you would *expect* to use `reactive`. It’s very easy to incorrectly use `reactive` in this way and lose reactivity without realizing what’s happening.

### Template Refs

Reassigning values can also cause some issues when using the simplest form of template refs:

```html
<template>
  <div>
    <h1 ref="heading">This is my page</h1>
  </div>
</template>
```

In this case, we can’t use a `reactive` object at all:

```js
const heading = reactive(null);
watchEffect(() => console.log(heading));
// "null"
```

When the component is first instantiated, this will log out `null`, because `heading` has no value yet. But when the component is mounted and our `h1` is created, it will not trigger. The `heading` object becomes a new object, and our watcher loses track of it. The reference to the previous reactive object is overwritten.

We need to use a `ref` here:

```js
const heading = ref(null);
watchEffect(() => console.log(heading.value));
// "null"
```

This time, when the component is mounted it will log out the element. This is because only a `ref` can be reassigned in this way.

It *is* possible to use `reactive` in this scenario, but it requires a bit of extra syntax using [function refs](https://vuejs.org/guide/essentials/template-refs.html#function-refs):

```tsx
<template>
  <div>
    <h1 :ref="(el) => { heading.element = el }">This is my page</h1>
  </div>
</template>
```

Then our script would be written as so, using the `el` property on our reactive object:

```tsx
const heading = reactive({ el: null });
watchEffect(() => console.log(heading.el));
// "null"
```

_Alex Vipond wrote a fantastic book on using the *function ref pattern* to create highly reusable components in Vue ([something I know quite a bit about](https://michaelnthiessen.com/reusable-components)). It’s eye-opening, and I’ve learned a ton from this book, so do yourself a favour and grab it here: [Rethinking Reusability in Vue](https://rethinking-reusability-in-vue.alexvipond.dev/)_

</aside>

### Destructuring Values

Destructuring a value from a `reactive` object will break reactivity, since the reactivity comes from the object itself and not the property you’re grabbing:

```js
const myObj = reactive({ prop1: 'hello', prop2: 'world' });
const { prop1 } = myObj;

// prop1 is just a plain String here
```

You must use `toRefs` to convert all of the properties of the object into refs first, and then you can destructure without issues. This is because the reactivity is inherent to the `ref` that you’re grabbing:

```js
const myObj = reactive({ prop1: 'hello', prop2: 'world' });
const { prop1 } = toRefs(myObj);

// Now prop1 is a ref, maintaining reactivity
```

Using `toRefs` in this way lets us destructure our props when using `script setup` without losing reactivity:

```js
const { prop1, prop2 } = toRefs(defineProps({
  prop1: {
    type: String,
    required: true,
  },
  prop2: {
    type: String,
    default: 'World',
  },
}));
```

### Composing ref and reactive

One interesting pattern is combining `ref` and `reactive` together.

We can take a bunch of refs and group them together inside of a `reactive` object:

```js
const lettuce = ref(true);
const burger = reactive({
  // The ref becomes a property of the reactive object
  lettuce,
});

// We can watch the reactive object
watchEffect(() => console.log(burger.lettuce));

// We can also watch the ref directly
watch(lettuce, () => console.log("lettuce has changed"));

setTimeout(() => {
  // Updating the ref directly will trigger both watchers
  // This will log: `false`, 'lettuce has changed'
  lettuce.value = false;
}, 500);
```

We’re able to use the `reactive` object as we’d expect, but we can also reactively update the underlying refs even without accessing the `reactive` object we’ve created. However you access the underlying properties, they reactively update everything else that’s “hooked up” to it.

I’m not sure this pattern is better than simply putting a bunch of refs in a plain JS object, but it’s there if you need it.

### Organizing State with Ref and Reactive

One of the best uses for `reactive` is to manage state.

With `reactive` objects we can organize our state into objects instead of having a bunch of refs floating around:

```js
// Just a bunch a refs :/
const firstName = ref('Michael');
const lastName = ref('Thiessen');
const website = ref('michaelnthiessen.com');
const twitter = ref('@MichaelThiessen');
```

```js
const michael = reactive({
  firstName: 'Michael',
  lastName: 'Thiessen',
  website: 'michaelnthiessen.com',
  twitter: '@MichaelThiessen',
});
```

Passing around a single object instead of lots of refs is much easier, and helps to keep our code organized.

There’s also the added benefit that it’s much more readable. When someone new comes to read this code, they know immediately that all of the values inside of a single `reactive` object must be related somehow — otherwise, why would they be together?

With a bunch a refs it’s much less clear as to how things are related and how they might work together (or not).

However, an even better solution for grouping related pieces of reactive state might be to create a simple composable instead:

```js
// Similar to defining a reactive object
const michael = usePerson({
  firstName: 'Michael',
  lastName: 'Thiessen',
  website: 'michaelnthiessen.com',
  twitter: '@MichaelThiessen',
});

// We usually return refs from composables, so we can destructure here
const { twitter } = michael;
```

This gives us the benefits of both worlds.

Not only can we group our state together, but it’s even *more* explicit that these are things that go together. And since we’re returning an object of refs from our composable (you’re doing that, right?) we can use each piece of state individually if we want.

We have the added benefit that we can co-locate methods with our composable, too. So state changes and other business logic can be centralized and easier to manage.

Of course, this may be a little more than what you need, in which case using `reactive` is perfectly fine. You may also find yourself wondering, “why not just use Pinia for this?”, and you’d certainly have a valid point.

The point is this:

Using `reactive` gives us another great option for organizing our state.

### Wrapping Non-Reactive Libraries and Objects

In talking with Eduardo about this debate, he mentioned that the only time he uses `reactive` is for wrapping collections (besides arrays):

```tsx
const set = reactive(new Set());

set.add('hello');
set.add('there');
set.add('hello');

setTimeout(() => {
  set.add('another one');
}, 2000);
```

Because Vue’s reactivity system uses proxies, this is a really easy way to take an existing object and spice it up with some reactivity.

You can, of course, apply this to any other libraries that aren’t reactive. Though you may need to watch out for edge cases here and there.

### Refactoring from Options API to Composition API

It also appears that `reactive` is really useful when refactoring a component to use the Composition API:

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Transition from Vue2 is much easier if you go with reactive, especially if you have many options to update. You just copy and paste them and it works, yet if I have to choose - ref is the way :)</p>&mdash; Plamen Zdravkov (@pa4oZdravkov) <a href="https://twitter.com/pa4oZdravkov/status/1613558580245823489?ref_src=twsrc%5Etfw">January 12, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

I haven’t tried this myself yet, but it does make sense. We don’t have anything like `ref` in the Options API, but `reactive` works very similarly to reactive properties inside of the `data` field.

Here, we have a simple component that updates a field in component state using the Options API:

```tsx
// Options API
export default {
  data() {
    username: 'Michael',
    access: 'superuser',
    favouriteColour: 'blue',
  },
  methods: {
    updateUsername(username) {
      this.username = username;
    },
  }
};
```

The simplest way to get this working using the Composition API is to copy and paste everything over using `reactive`:

```tsx
// Composition API
setup() {
  // Copy from data()
  const state = reactive({
    username: 'Michael',
    access: 'superuser',
    favouriteColour: 'blue',
  });

  // Copy from methods
  updateUsername(username) {
    state.username = username;
  }

  // Use toRefs so we can access values directly
  return {
    updateUsername,
    ...toRefs(state),
  }
}
```

We also need to make sure we change `this` → `state` when accessing reactive values, and remove it entirely if we need to access `updateUsername`.

Now that it’s working, it’s much easier to continue refactoring using `ref` if you want to. But the benefit of this approach is that it’s straightforward (possibly simple enough to automate with a codemod or something similar?).

### They’re just different

After going through all of these examples it should be pretty clear that *if we really had to,* we could write perfectly fine Vue code with just `ref` or just `reactive`.

They’re equally capable — they’re just *different*.

Keep this in mind as we explore the debate between `ref` and `reactive`.

__Keep reading Act 2 and Act 3 of this article [on my blog](https://michaelnthiessen.com/ref-vs-reactive/#act-2-the-ref-vs-reactive-debate).__