Quick Tip: Breaking Vue's Reactivity


VueJS is my front-end framework of choice. I use it at work and on various side projects. I love Vue’s opt-in style reactivity engine (as opposed to React’s opt-out style), but on occasion I find myself needing to purposely break the reactivity of a value.

One such situation that I have run into on multiple occasions recently is when using a confirmation dialog. It’s a good practice when deleting an object through a UI, to not immediately perform the delete on the initial click, but to first confirm the destructive action with the user. That’s because preventing errors is better than helping users recover from them. This principle is #5: Error Prevention on Jakob Nielson’s 10 Usability Heuristics for User Interface Design

Let’s say I have an object from a list selected and I click Delete. To confirm this action, I show a dialog that display’s the selected object’s name along with a question asking if the user is sure they want to delete it.

confirm dialog

To accomplish this, I save the selected object to a ref called selectedObj and I use selectedObj.value.name in the text of the confirmation.

If the user clicks “Yes, Delete”, we fire off a delete request, reset selectedObj to undefined, and close the dialog.

But because selectedObj is a reactive ref, as the dialog is fading out the name will disappear and the remaining text will shift suddenly, which is a pretty janky UX.

const selectedObj = ref();
const isConfirmOpen = ref(false);

async function deleteObject() {
  if (selectedObj.value?.id === undefined) return;
  await http.delete(`/api/item/${selectedObj.value.id}`);
  selectedObj.value = undefined;
  isConfirmOpen.value = false;
}

The simple solution is to make a non-reactive copy of the name before opening the dialog. Since the name value is a string, we can just copy it to a let and use that in the dialog’s text.

let confirmObjName = '';

function handleDeleteObject() {
  // make a copy of the preset name before opening the confirm modal
  confirmObjName = selectedObj.value?.name ?? '';
  isConfirmOpen.value = true;
}
<!-- button with click handler -->

<button type="button" @click="handleDeleteObject">Delete Object</button>
<!-- modal component -->

<DModal v-model:open="isConfirmOpen" @confirm="deleteObject">
  <p>
    Are you sure you want to delete
    <span class="font-semibold">{{ confirmObjName }}</span>? This action cannot be undone.
  </p>
</DModal>

If we needed to break reactivity of the entire selectedObj we could copy it using Vue’s toRaw method.

let selectedObjCopy = toRaw(selectedObj.value);

However, this is not necessary if all we are copying is a primitive value like a string.

Now when I set selectedObj to undefined after sending the delete request, the name of the object will no longer disappear before the dialog closes.