ref() and reactive()
Create reactive primitives with ref(), wrap objects with reactive(), and understand when to use each and how to access ref values via .value.
ref() and reactive() is a free Frontend Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Reactivity in Vue 3
Vue 3's Composition API uses reactive primitives to track state. Two key functions: ref() for primitive values, and reactive() for objects. Changes to these automatically update the UI.
ref() for Primitives
ref(value) wraps a value in a reactive container. Access and mutate the value via .value in script. In templates, Vue unwraps refs automatically — no .value needed.
<script setup>
import { ref } from 'vue';
const count = ref(0);
const name = ref('Alice');
function increment() {
count.value++; // .value in script
}
</script>
<template>
<p>{{ count }}</p> <!-- no .value in template -->
<button @click="increment">+</button>
</template>reactive() for Objects
reactive(object) returns a deeply reactive proxy of the object. You access properties directly — no .value needed anywhere.
<script setup>
import { reactive } from 'vue';
const form = reactive({
email: '',
password: '',
rememberMe: false
});
// Mutate directly:
form.email = 'user@example.com';
</script>
<template>
<input v-model="form.email">
</template>ref vs reactive — When to Use Which
Use ref() for: primitives (string, number, boolean), nullable values, returning from composables (easier to destructure). Use reactive() for: related data that belongs together in one object.
reactive() Destructuring Pitfall
Destructuring a reactive object loses reactivity because the extracted properties are plain values, not reactive refs.
const state = reactive({ count: 0 });
const { count } = state; // NOT reactive
count++; // doesn't update Vue's tracking
// Fix: use toRefs() to destructure reactively:
import { toRefs } from 'vue';
const { count } = toRefs(state); // count is now a ref
count.value++;toRef and toRefs
toRef(obj, 'key') creates a ref linked to one property. toRefs(obj) converts all properties of a reactive object to individual linked refs — safe to destructure.
import { toRefs } from 'vue';
const user = reactive({ name: 'Alice', age: 30 });
const { name, age } = toRefs(user);
// name and age are refs, still linked to user
name.value = 'Bob'; // user.name also becomes 'Bob'ref() for DOM Elements
Use ref(null) with a matching ref attribute in the template to get a reactive reference to a DOM element or child component instance.
<script setup>
import { ref, onMounted } from 'vue';
const inputEl = ref<HTMLInputElement | null>(null);
onMounted(() => {
inputEl.value?.focus(); // now the DOM element is available
});
</script>
<template>
<input ref="inputEl" type="text">
</template>isRef and unref
isRef(val) checks if a value is a ref. unref(val) unwraps a ref or returns the value as-is if it's not a ref. Useful in composables that accept both refs and plain values.
import { isRef, unref } from 'vue';
function double(val: Ref<number> | number) {
return unref(val) * 2; // works with both ref and plain number
}shallowRef and shallowReactive
shallowRef() is only reactive at the top level (not deeply). shallowReactive() same for objects. Use for performance optimisation when deep reactivity isn't needed (e.g., large immutable data structures).
Mutating reactive() vs Replacing
With reactive(), mutate properties directly. You cannot replace the entire object — reactive() needs the same reference. For replacing, use ref() which allows ref.value = newObject.
const state = reactive({ items: [] });
state.items.push(newItem); // OK
state.items = [newItem]; // also OK (mutating the property)
// state = reactive({ items: [newItem] }); // LOSES REACTIVITY — can't reassignDeep Reactivity
reactive() is deeply reactive by default — Vue tracks changes to nested objects and arrays. This is convenient but can be expensive for very large objects. Consider shallowReactive for performance-sensitive cases.
Quick Check
Why does destructuring a reactive() object lose reactivity?
Recap: ref() and reactive()
ref() wraps primitives; access via .value in script, auto-unwrapped in templates. reactive() makes objects deeply reactive; mutate properties directly. Destructuring loses reactivity — use toRefs(). ref(null) for template element refs. shallowRef/shallowReactive for performance. isRef/unref for composable utilities.
Frequently asked questions
Is the “ref() and reactive()” lesson free?
Yes — the full text of “ref() and reactive()” is free to read here on the web, and the Frontend Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO.
What will I learn in “ref() and reactive()”?
Create reactive primitives with ref(), wrap objects with reactive(), and understand when to use each and how to access ref values via .value. You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “ref() and reactive()” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.