What this usually means
This bug almost always indicates Vue’s change detection isn’t picking up your update. That usually means you’ve mutated data in a way Vue can’t track (direct assignment to a non-reactive property or array/object mutation Vue can’t observe), or that the update happened outside Vue’s observation (e.g., a callback from a non-reactive source or direct DOM manipulation). Sometimes, a dependency is missing from a computed or watcher. These aren’t always classic user errors—external APIs, legacy plugins, and tricky edge cases regularly trip up experienced engineers.
The first ten minutes — establish facts before touching code.
- 1Open Vue Devtools and confirm if the reactive data changes in the component tree when you expect it to.
- 2Check the actual mutation: Did you use Vue.set() or assign a property that wasn't declared in data()? For arrays, did you use push, splice, etc.?
- 3Inspect if the property exists on the Vue instance before update (console.log(this.$data))—missing properties aren’t reactive.
- 4Look for assignment outside Vue’s reactivity (plain object, window event, or callback).
- 5Temporarily add :key="Math.random()" to the element or component; if this forces a rerender, you likely have a reactivity issue.
- 6Scan for direct property mutation on props in child components (bad—breaks reactivity).
The specific files, logs, configs, and dashboards that usually own this bug.
- searchsrc/components/YourComponent.vue (focus on data(), computed, methods)
- searchVue Devtools (Component inspector, State tab)
- searchAny watcher or computed functions for missing dependencies
- searchConsole output of this.$data and mutated values
- searchExternal data source code (e.g., fetch callbacks, WebSockets)
- searchBuild output for minified/stripped properties
- searchThird-party plugin code that may bypass Vue's observation
Practical causes, not theory. These are the things you will actually find.
- warningAdding a new property to an object that wasn't declared in data() (Vue 2 limitation)
- warningDirectly assigning to an index of an array (e.g., arr[1] = x) instead of Vue.set() or splice()
- warningUsing Object.assign or spread operator on objects after initialization (replaces reactive object with a plain one)
- warningAsync callback mutates data outside Vue’s tracking context
- warningProps mutated directly inside child component
- warningReactivity lost due to destructuring assignment (e.g., const { foo } = this.someObject)
Concrete fix directions. Pick the one that matches your root cause.
- buildDeclare all reactive properties up front in data(), even with placeholder values.
- buildUse Vue.set(obj, key, value) or this.$set(obj, key, value) when adding new properties.
- buildFor arrays, always use push, splice, or Vue.set to update—never assign by index.
- buildWrap async mutations in this.$nextTick if needed to force Vue to update after change.
- buildNever mutate props inside a child component—emit events upward and mutate in the parent.
- buildIf you must replace reactive objects, assign to a new object via Vue.set or reactivity-friendly method.
A fix you cannot prove is a guess. Close the loop.
- verifiedUpdate the data property and confirm template re-renders live without refresh.
- verifiedCheck Vue Devtools: property changes should immediately reflect in both devtools and UI.
- verifiedWrite a test with a DOM assertion confirming the visible state matches the expected value.
- verifiedIf in a loop/list, add and remove items and observe the rendered output changes as expected.
- verifiedReload the page and re-test to be sure the fix survives a fresh data initialization.
- verifiedRemove any :key hacks or force updates and see if normal reactivity now works.
Things that make this bug worse or harder to find.
- warningAdding new properties to objects after creation without Vue.set() (Vue 2).
- warningReplacing the entire data object without going through Vue’s reactivity (Object.assign, etc).
- warningMutating props inside the child component and expecting parent to update.
- warningAssuming async API changes will trigger updates without checking reactivity context.
- warningForgetting to declare all expected properties in the data() function.
- warningLeaving hidden direct DOM manipulations (document.getElementById, etc).
Silent State Stale: Array Push Not Reflected in Vue List
Timeline
- 10:03User reports UI list doesn't show new items after API call.
- 10:05Engineer confirms API success; item appears in console.log(array).
- 10:07Vue Devtools shows data updated, but UI remains unchanged.
- 10:09:key workaround forces UI update, but root cause still unclear.
- 10:13Audit reveals mutation: arr[idx] = newItem instead of arr.splice(idx, 1, newItem).
- 10:16Patch deployed using Vue.set for array mutation.
- 10:19UI updates normally; workaround removed.
I got pinged by QA because the dashboard's list of jobs wouldn't update after a user added a new job via the API. The backend confirmed that the data saved and even log output in the component showed the array had the new item—a classic red herring.
Vue Devtools made things murky: the component's array *did* change, but the UI stubbornly refused to reflect it. I tried the usual quick hack (:key with random) and, sure enough, forcing a re-render made the new item appear. Clearly a reactivity bug, not a rendering one.
Reading through the add handler, I spotted the actual problem: the code directly assigned to an array index (`jobs[jobIdx] = jobObj`), which Vue 2 can't observe. Switching this to `Vue.set(jobs, jobIdx, jobObj)` immediately fixed the refresh issue. Lesson reinforced: Vue's reactivity system is picky about *how* you update data.
Root cause
Direct assignment to an array index, which Vue 2 can't detect, so the view didn't update.
The fix
Replaced arr[idx] = newItem with Vue.set(arr, idx, newItem) in all mutations.
The lesson
Vue's reactivity system isn't magic; update patterns matter. Always use Vue.set for object/array mutations.
Vue (especially v2) uses Object.defineProperty to make properties reactive at initialization. If you assign a new property later, or mutate an array by index, Vue can't intercept that change. That's why updates outside initialization or tracked mutations don't make it to the DOM.
Vue 3 solves some cases with proxies, but legacy code and many real-world stacks run into these boundaries. If the property wasn't present at first render, Vue simply won't know to watch it.
Async updates (like setTimeout, Promise.resolve chains, or socket callbacks) can sometimes run outside Vue’s observation loop. If your API or event handler mutates state, check if it’s integrated through Vue (e.g., via a method) or just attached to a plain object.
Forcing an update with this.$nextTick or wrapping the mutation in a Vue method is sometimes necessary to re-enter the reactivity flow. Watch for this especially with 3rd party libraries.
If a computed property or watcher doesn't update, check if all referenced properties are actually reactive and correctly listed. Dependency omission (using a non-reactive property or destructuring without reactivity) means the computed won't recalculate when you expect.
Rewrite computeds and watchers to directly reference reactive properties—never destructured values or plain objects.
Always confirm the property exists in the component’s this.$data at initialization. If not, Vue won't track it.
Use Vue Devtools to inspect whether mutations are visible in the component tree. If they are, but the DOM isn't updating, likely a render or mutation pattern issue.
Lastly, check your build process. Rare edge: tree-shaking or minification sometimes strips 'unused' properties from data if not referenced at startup.
Frequently asked questions
Does Vue 3 still have reactivity caveats for new properties?
Vue 3 uses proxies, greatly reducing but not eliminating these edge cases. For most objects, adding new properties is now reactive—but for arrays, you still need to use push/splice or replace the array for updates to be tracked by the UI.
Why does logging the data show the correct value but the template doesn't update?
Console.log shows JavaScript values, not tracked reactivity. You can mutate plain objects all you want—unless Vue is watching, your template won't re-render.
Is it okay to mutate props in the child component?
No. Props are owned by the parent. Mutating them in the child either fails silently or breaks reactivity—always emit events to the parent for prop changes.
When should I use Vue.set or this.$set?
Any time you add a new property to an object or assign an array index in Vue 2. It's often unnecessary in Vue 3, but using it is safe and avoids subtle bugs.