---
title: useMutation composable
related:
  - title: v-mutation directive
    path: ../vue-directives/mutation.md
  - title: useElementSize composable
    path: use-element-size.md
  - title: useIntersection composable
    path: use-intersection.md
---
The `useMutation()` composable watches for changes being made to the DOM tree of an element (or of a component): child nodes added or removed, attributes changed, text changed. Under the hood it uses the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).

It is the setup-code counterpart of the [v-mutation](../vue-directives/mutation.md) directive. Use the composable when you want to observe your component's own root, or any element or component ref, without touching the template.

> [!NOTE]
> On the server-side of SSR or SSG modes, the composable never observes anything.

> [!TIP]
> **Outside of a component**
>
> The composable can also be called outside of `setup()`: in a boot file, a store or a plain module. There is no component root to fall back on and no mount to wait for, so supply a `target` (an element, or a ref or getter of one); the observation starts right away and nothing stops it by itself: call `stopMutation()` when you are done.

## Syntax

```js
import { useTemplateRef } from 'vue'
import { useMutation } from 'quasar'

setup () {
  const target = useTemplateRef('target') // an Element or a component

  const { mutationRecords, stopMutation } = useMutation({
    // all optional:
    target,               // (default: the component's own root element)

    // MutationObserver options (default: none set, which observes
    // every kind of change)
    childList: true,
    attributes: true,
    characterData: true,
    subtree: true,
    attributeOldValue: true,
    characterDataOldValue: true,
    attributeFilter: [ 'class', 'style' ],

    once: true,           // stop after the first batch of records (default: false)
    disabled: true,       // pause observing (default: false)
    onMutation (records) { // called with the Array of MutationRecord
      // return false to stop observing for good
    }
  })

  // ...
}
```

```ts
function useMutation(
  options?: MaybeRefOrGetter<{
    target?: MaybeRefOrGetter<
      Element | ComponentPublicInstance | null | undefined
    >
    childList?: boolean
    attributes?: boolean
    characterData?: boolean
    subtree?: boolean
    attributeOldValue?: boolean
    characterDataOldValue?: boolean
    attributeFilter?: string[]
    once?: boolean
    disabled?: boolean
    onMutation?: (records: MutationRecord[]) => false | void
  }>
): {
  mutationRecords: ShallowRef<MutationRecord[]>
  stopMutation: () => void
}
```

Without a `target`, the composable observes the root element of the component it is called in, as of the moment the component gets mounted. A component rendering a fragment (multiple root nodes) has no root element to observe, so supply a `target` there.

Reading the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) first will be best in your understanding of the observing options. When none of `childList`, `attributes`, `characterData`, `subtree`, `attributeOldValue`, `characterDataOldValue` or `attributeFilter` is set, every kind of change gets observed, with the old values included, the same as the [v-mutation](../vue-directives/mutation.md) directive without modifiers. Setting any of them observes only what you ask for; at least one of `childList`, `attributes` or `characterData` must then be set (or `attributeFilter`, which implies `attributes`), as `subtree` or the old-value flags alone make the native observer throw.

Every batch of [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) that the browser delivers after the changes lands in the reactive `mutationRecords` (the last batch only, so it never grows) and is handed to the `onMutation` handler. Returning `false` from the handler stops the observation for good. With `once`, the observation stops by itself after the first batch.

`stopMutation()` ends the observation for good. You will rarely need it, as the composable stops by itself when the component gets destroyed.

> [!WARNING]
> **Warning! Avoid feedback loops**
>
> Whatever your handler (or the reactive state it updates) does to the observed DOM is a new mutation, which calls the handler again, and so on without end: rendering the state inside the observed element, toggling a class on it, appending a node to it. Keep the effects of the handler out of the observed element, or observe only what you need (the element's own `attributes`, its direct `childList`).

## Changing the options while running

The options can be a plain Object, a Ref or a getter Function. A plain Object is read once. With a Ref or a getter, the composable tracks whatever reactive state the options read and re-applies them whenever that state changes, so you never call anything to "update" it:

- toggling `disabled` pauses and resumes the observation (a `once` that already fired stays off for as long as `once` holds; setting `once` back to `false` starts observing again)
- pointing `target` to another element (or letting a template ref change through `v-if`) follows it
- changing what gets observed applies right away, keeping the changes not delivered yet
- swapping `onMutation` takes effect from the next batch

```js
import { ref } from 'vue'
import { useMutation } from 'quasar'

setup () {
  const paused = ref(false)

  useMutation(() => ({
    childList: true,
    disabled: paused.value,
    onMutation (records) { /* ... */ }
  }))

  // ...
}
```

## Example

Observing a list through a template ref: add, remove and rename items, and see every batch of changes as the browser reports it through `mutationRecords`:

Example "Observing a list":

```vue
<template>
  <div class="q-gutter-sm q-mb-md">
    <q-btn color="primary" push label="Add item" @click="addItem" />
    <q-btn
      color="negative"
      push
      label="Remove last"
      :disable="items.length === 0"
      @click="removeItem"
    />
    <q-btn
      color="secondary"
      push
      label="Rename first"
      :disable="items.length === 0"
      @click="renameItem"
    />
    <q-toggle v-model="paused" label="Paused" />
  </div>

  <q-list ref="listRef" bordered separator class="q-mb-md">
    <q-item v-for="item in items" :key="item.id">
      <q-item-section>{{ item.label }}</q-item-section>
    </q-item>
    <q-item v-if="items.length === 0">
      <q-item-section class="text-grey">Empty list</q-item-section>
    </q-item>
  </q-list>

  <div class="q-gutter-sm">
    <q-badge :label="`batches: ${batches}`" />
    <q-badge color="secondary" :label="`last batch: ${lastBatch}`" />
    <q-badge
      color="accent"
      :label="`records in it: ${mutationRecords.length}`"
    />
  </div>
</template>

<script setup>
import { ref, useTemplateRef } from 'vue'
import { useMutation } from 'quasar'

const listRef = useTemplateRef('listRef') // a component: its root gets observed

const items = ref([])
const paused = ref(false)
const batches = ref(0)
const lastBatch = ref('none')

let nextId = 1

const { mutationRecords } = useMutation(() => ({
  target: listRef,
  childList: true,
  characterData: true,
  subtree: true,
  disabled: paused.value,
  onMutation(records) {
    batches.value++
    lastBatch.value = records
      .map(record => record.type)
      .filter((type, index, list) => list.indexOf(type) === index)
      .join(', ')
  }
}))

function addItem() {
  items.value.push({ id: nextId, label: `Item ${nextId}` })
  nextId++
}

function removeItem() {
  items.value.pop()
}

function renameItem() {
  items.value[0].label += ' (renamed)'
}
</script>
```
