Vue Array Mutation Not Reactive — UI Does Not Update
Fix non-reactive array updates in Vue by replacing the array or using reactive-aware methods, and avoid mutating index positions directly.
Symptoms
- Pushing to an array does not update the UI
- Assigning by index (arr[0] = x) does not re-render
- Changing array length does not trigger an update
Possible causes
- A plain (non-reactive) array is mutated and Vue is unaware
- Direct index assignment bypasses reactive tracking in some setups
- The array is destructured into a plain variable, losing reactivity
Diagnosis steps
Confirm the array is reactive
import { isReactive } from 'vue'; console.log(isReactive(arr));Interpretation
If false, the array was copied or created outside a reactive context.
Use a reactive-aware method
arr.value = [...arr.value, newItem]Interpretation
Replacing the array reference reliably triggers reactivity in all setups.
Root cause
Vue 3 tracks reactive arrays through Proxy, and its instrumented array methods (push, splice, etc.) notify dependents. Mutations that bypass these mechanisms—such as assigning to an index of a plain array or copying the array into a non-reactive binding—do not notify the renderer.
Fix
Keep arrays inside ref/reactive, use Vue-aware methods, replace the whole array when in doubt (arr.value = [...arr.value, x]), and use toRefs or computed for derived views.
Risk note
Deep-watching large arrays is expensive; prefer computed or keyed updates over watch with deep:true.
Verify the fix
Mutate the array through a reactive-aware path and confirm the UI updates; assert the rendered list length matches the array length.
Symptoms
When you hit “Vue Array Mutation Not Reactive — UI Does Not Update”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.
Observed symptoms include:
- Pushing to an array does not update the UI
- Assigning by index (arr[0] = x) does not re-render
- Changing array length does not trigger an update
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Confirm the array is reactive
import { isReactive } from 'vue'; console.log(isReactive(arr));
If false, the array was copied or created outside a reactive context.
Step 2: Use a reactive-aware method
arr.value = [...arr.value, newItem]
Replacing the array reference reliably triggers reactivity in all setups.
Likely contributors, in order of frequency:
- A plain (non-reactive) array is mutated and Vue is unaware
- Direct index assignment bypasses reactive tracking in some setups
- The array is destructured into a plain variable, losing reactivity
Root Cause
Vue 3 tracks reactive arrays through Proxy, and its instrumented array methods (push, splice, etc.) notify dependents. Mutations that bypass these mechanisms—such as assigning to an index of a plain array or copying the array into a non-reactive binding—do not notify the renderer.
The Fix
Keep arrays inside ref/reactive, use Vue-aware methods, replace the whole array when in doubt (arr.value = […arr.value, x]), and use toRefs or computed for derived views.
Watch out: Deep-watching large arrays is expensive; prefer computed or keyed updates over watch with deep:true.
<script setup>
import { ref } from 'vue';
const items = ref([]);
function add(item) {
items.value.push(item); // instrumented, reactive
}
function updateFirst(value) {
items.value = [value, ...items.value.slice(1)]; // replace to be safe
}
</script>
<template>
<ul><li v-for="i in items" :key="i.id">{{ i.name }}</li></ul>
</template>
Verify and Prevent
Mutate the array through a reactive-aware path and confirm the UI updates; assert the rendered list length matches the array length.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.