---
contentId: 9eb990da-ce9f-42ea-9836-5bb1fca28691
title: Using Pinia Colada in Modals Without Spoiling the UX
description: Far from being straightforward but essential to provide a good user experience, you'll understand when to await for invalidated queries in Pinia Colada.
date: 2025-06-07
---

Since months, I've been using [**Pinia Colada**](https://pinia-colada.esm.dev/). I discovered it while searching for an elegant solution for data fetching on my personal website (the one you're reading right now). Since then, I've used it in all my projects that require data from an API. It's easy to use, functions seamlessly, and enables me to create better experiences with its built-in cache, stale-while-revalidate feature, and the ease of implementing optimistic updates.

**Pinia Colada** is a data fetching layer for [**Pinia**](https://pinia.vuejs.org/), the intuitive store for **Vue.js**, making asynchronous state management a breeze. You should definitely give it a try. I can't start a new project without it anymore.

However, I recently faced a challenge using **Pinia Colada** in a modal. Let me explain the problem and how I solved it.

## The problem

Before explaining, take a look at the following video:

<figure>
  <video autoplay loop muted playsinline>
    <source src="https://images.soubiran.dev/posts/using-pinia-colada-in-modals-without-spoiling-the-ux/the-problem.mp4" type="video/mp4">
  </video>
  <figcaption>The modal closes before the data is updated.</figcaption>
</figure>

Can you spot the problem?

When I submit the form in the modal, the "Submit" button is disabled, and a loading spinner appears. So far, so good. But then, the modal closes before the data updates on the page. The user has to wait, yet no feedback is provided, resulting in a terrible user experience. The user remains unaware whether the action was successful.

You can find the code for this video on <GitHubLink repo="barbapapazes/pinia-colada-await-invalidate-queries" />.

With this high-level overview, let's delve into the technical details of the problem and its causes.

The **Pinia Colada** code I wrote to mutate the data is as follows:

```ts
const open = ref(false)
const { mutate: createComment, isLoading: isCreatingComment } = useMutation({
  mutation: () => $fetch('/api/comments', {
    method: 'POST',
    body: comment.value
  }),
  onSettled: () => {
    queryCache.invalidateQueries({ key: ['comments'] })
  },
  onSuccess: () => {
    comment.value.text = ''
    open.value = false
  }
})
```

The `open` variable controls the modal's visibility.

The user opens the modal, fills the form, and clicks the "Submit" button. The `createComment` mutation is then called, sending data to the API via the `mutate` function. Upon success, the `onSuccess` callback resets the form and closes the modal. Finally, the `onSettled` callback invalidates the `comments` query to refetch data from the API.

The **Pinia Colada** query looks like this:

```ts
const { state } = useQuery({
  key: ['comments'],
  query: () => $fetch('/api/comments')
})
```

The issue is that `onSettled`, which invalidates the query and re-fetches the data, is called after `onSuccess`, which closes the modal. Consequently, the user can see the modal closing before the data appears on the page, causing confusion. "Where is my comment? Did it work?"

Could we simply move `open.value = false` to `onSettled`, after the query invalidation? Unfortunately, no.

## Multiple solutions

To solve the problem, two solutions came to mind:

- **Optimistic Updates**: This is the best solution for user experience, immediately updating the UI, but results in more complex code. It's not always feasible, especially if the API returns data different from what was sent. For example, with a comment system allowing markdown, the API returns formatted markdown, but the user sends raw markdown. In such cases, optimistic updates aren't viable, and a loading state is the only solution.
- **Loading State**: This is the simplest solution. Disable the "Submit" button, display a loading spinner, and wait for the API response. It's the most common and easiest to implement. However, in a modal, it requires adjustments compared to an inline form.

For my project, optimistic updates weren't an option, so I had to use a loading state. Yet while implementing it, I faced this problem. No matter how many times I read the **Pinia Colada** documentation, I couldn't come up with a solution. At some point, I came up with an idea. I jumped to the documentation to check if my understanding could be a viable solution.

## Awaiting for invalidated queries

The documentation includes a section about ["To `await` or not to `await`"](https://pinia-colada.esm.dev/guide/query-invalidation.html#To-await-or-not-to-await) that states:

> In mutations, it's possible to await within the different hooks. This will effectively delay the resolution or rejection of the mutation and its asyncStatus.

This was the key. I had never understood the case for awaiting invalidated queries, but it became clear with this problem. By awaiting the invalidated queries, I could delay closing the modal until the data was re-fetched and available on the page.

Here's the code to implement this solution:

```ts
const open = ref(false)
const { mutate: createComment, isLoading: isCreatingComment } = useMutation({
  mutation: () => $fetch('/api/comments', {
    method: 'POST',
    body: comment.value
  }),
  onSettled: async (_, error) => {
    await queryCache.invalidateQueries({ key: ['comments'] })

    if (!error) {
      comment.value.text = ''
      open.value = false
    }
  },
})
```

Rather than using `onSuccess` to close the modal, I moved it to `onSettled`, adding an `await` before the cache invalidation. Thus, the modal closes only after the data is re-fetched and displayed on the page. Simple, elegant, and effective.

<figure>
  <video autoplay loop muted playsinline>
    <source src="https://images.soubiran.dev/posts/using-pinia-colada-in-modals-without-spoiling-the-ux/the-solution.mp4" type="video/mp4">
  </video>
  <figcaption>The modal closes after the data is updated.</figcaption>
</figure>

## Finally

This small issue underscores two significant points:

- Every detail impacts user experience. Small nuances can create substantial differences.
- Read the documentation, again and again, even if it can seem irrelevant initially, can help solve problems as I did here.

I hope this article clarifies this usage of **Pinia Colada** in modals without degrading user experience. If you have questions or suggestions, feel free to leave a comment below.

To see the code in action, you can find it on <GitHubLink repo="barbapapazes/pinia-colada-await-invalidate-queries" />.
