Chapter 87: Vue ‘data’ Option
The data option
This is the original, classic way to define local reactive state in a Vue component — the way Vue worked from day one (Vue 1 → Vue 2 → early Vue 3), and the way millions of legacy projects, tutorials, and job interview questions still use it in 2026.
Even if you personally write only <script setup> + Composition API today (which is the modern recommendation), you must understand the data() option properly because:
- You will read/maintain old code written this way
- Many job interviews still ask about it (especially in India)
- Some plugins, mixins, and global component patterns still rely on it
- Vue Devtools still shows data in this format
So let’s go through it step by step — like I’m pair-programming with you and explaining both the old world and why we mostly moved away from it.
1. What is the data option? (Very simple mental model)
data is a function that returns a plain JavaScript object.
Vue takes that object, makes every property inside it reactive, and attaches it to the component instance.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
export default { data() { return { count: 0, name: 'Rahul', isLoading: false, user: { age: 28, city: 'Nellore' }, todos: ['Learn Vue', 'Master reactivity'] } } } |
After Vue processes this:
- this.count becomes reactive → changing it triggers re-render
- this.user.age is also reactive (deep reactivity)
- You never write this.$data.count — just this.count
Vue internally does something like:
|
0 1 2 3 4 5 6 7 |
this.$data = reactive(returnedObjectFromData()) this.count === this.$data.count // true |
2. The #1 Golden Rule – Why data must be a function
Never write data as an object — always as a function.
Wrong (very common beginner mistake):
|
0 1 2 3 4 5 6 7 8 |
data: { count: 0 } |
→ All instances of this component share the same object → changing count in one instance changes it in all instances → chaos
Correct:
|
0 1 2 3 4 5 6 7 8 9 10 |
data() { return { count: 0 } } |
→ Every time Vue creates a new component instance, it calls data() → gets a fresh new object → each instance has its own independent state
3. Real, Complete Example – Counter + Todo List in Options API
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<template> <div class="counter-demo"> <h2>Options API – data() example</h2> <p>Count: {{ count }}</p> <button @click="increment">+1</button> <button @click="reset">Reset</button> <h3>Todos</h3> <input v-model="newTodo" @keyup.enter="addTodo" placeholder="Add todo..." /> <ul> <li v-for="todo in todos" :key="todo"> {{ todo }} <button @click="removeTodo(todo)">×</button> </li> </ul> </div> </template> <script> export default { name: 'CounterDemo', // This is the data option – must return fresh object every time data() { return { count: 0, newTodo: '', todos: ['Learn Vue', 'Understand data()'] } }, methods: { increment() { this.count++ // reactive – triggers re-render }, reset() { this.count = 0 }, addTodo() { if (!this.newTodo.trim()) return this.todos.push(this.newTodo.trim()) // array push → reactive this.newTodo = '' }, removeTodo(todo) { this.todos = this.todos.filter(t => t !== todo) // new array → reactive } }, // Just to show you can access $data (but you normally don't need to) mounted() { console.log('Mounted – my data is:', this.$data) // → { count: 0, newTodo: '', todos: […], ... } } } </script> <style scoped> .counter-demo { padding: 2rem; max-width: 500px; margin: 2rem auto; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); } button { margin: 0.5rem; padding: 0.6rem 1.2rem; } ul { list-style: none; padding: 0; } li { padding: 0.8rem; background: #f8fafc; margin: 0.5rem 0; border-radius: 6px; display: flex; justify-content: space-between; align-items: center; } </style> |
4. Important Rules & Gotchas (2026 Must-Know)
| Rule / Gotcha | Correct Behavior / Best Practice |
|---|---|
| data must be a function | data() { return { … } } — never data: { … } |
| Returned object must be plain JS | No refs, no classes, no functions — just plain data (Vue makes it reactive) |
| Properties become reactive automatically | this.count++ → re-render; this.todos.push() → re-render |
| Deep reactivity | Nested objects/arrays are reactive too (this.user.age++ works) |
| Never mutate props in data | Bad: this.user = this.$props.user → breaks reactivity when parent changes prop |
| Can return computed values? | No — computed must go in computed option |
| Can I access this inside data()? | No — this is not available yet (use created() or mounted() for that) |
5. Modern Vue 3 – Why We Almost Never Use data() Anymore
In <script setup> (the 2026 standard):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<script setup> import { ref, reactive } from 'vue' const count = ref(0) const user = reactive({ name: 'Rahul', age: 28 }) const todos = ref(['Learn Vue', 'Master reactivity']) </script> |
→ No data() function → No this → No $data → Just normal JavaScript variables at the top level → Same reactivity, but much cleaner & type-safe with TypeScript
Vue still creates an internal $data-like structure behind the scenes — but you never see it or touch it.
Quick Summary Table – data() in 2026
| Question | Options API (legacy) | Composition API (<script setup>) | What you should do in new code |
|---|---|---|---|
| How to define local state? | data() { return { count: 0 } } | const count = ref(0) or reactive({}) | Composition API |
| Access in template | {{ count }} | {{ count }} | — |
| Access in methods/computed | this.count | count.value or state.count | — |
| Still used in 2026? | Yes — in legacy code, some plugins, interviews | Almost never (except legacy migration) | Avoid unless maintaining old code |
| TypeScript friendly? | Poor | Excellent (with ref<…>, reactive<…>) | Use Composition |
Final 2026 Advice from Real Projects
- In new projects → you should never write data() — use <script setup> + ref / reactive
- When you see data() → it means Options API (legacy style)
- Learn to read Options API — many jobs, open-source projects, tutorials still use it
- Never teach beginners data() as primary — start with <script setup>
- If migrating old code → gradually convert data() → ref / reactive, methods → normal functions, etc.
Your mini homework:
- Create the counter + todo component above in Options API
- Click buttons → see reactivity work
- Console.log this.$data in mounted → see the object
- Convert it to <script setup> → compare readability & TypeScript support
Any part confusing? Want to see:
- Full Options API vs Composition API side-by-side project?
- How data() looks in Vue Devtools?
- Common bugs when forgetting data() must be a function?
- Real component written in both styles?
Just tell me — we’ll convert and compare together step by step 🚀
