Quick Tip: Breaking Vue's Reactivity

ninjasoards

David Y Soards

Posted on March 7, 2024

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.

Image description

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
<!-- button with click handler -->

<button type="button" @click="handleDeleteObject">Delete Object</button>
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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

let selectedObjCopy = toRaw(selectedObj.value);
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
ninjasoards
David Y Soards

Posted on March 7, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related