skip to content

In Vue 3, when do onBeforeUpdate and onUpdated run, and what does the DOM show inside each of them?

level: juniorimportance: should knowfreq 55%

answer

  1. re-render, not first render
  2. old DOM versus patched DOM
  3. state edits before the render are safe
  4. child updated before parent updated

basics

~20 s

Both run only when an already-mounted component re-renders because reactive state it depends on changed. onBeforeUpdate runs before the patch, with the old DOM still in place; onUpdated runs after the patch, when the DOM shows the new state.

solid answer

~40 s

`onBeforeUpdate` and `onUpdated` belong to the **update phase**, so neither runs on the first render: that is the job of `onBeforeMount` and `onMounted`. When a reactive dependency of the component's render changes, Vue queues one re-render; right before it patches, it calls `onBeforeUpdate`, where the DOM still shows the previous state and it is safe to change component state, because the render that follows reads the new values. After the patch Vue queues `onUpdated` as a post-flush callback; the DOM now reflects the new state, and when the parent's patch also re-rendered a child (for example with new props), the child's `onUpdated` runs first. Neither hook is called during server-side rendering. `onUpdated` fires once per re-render, whatever caused it, so it is the wrong place to react to one specific change.

code

vue · 20 lines
vue
<script setup lang="ts">
import { ref, onBeforeUpdate, onUpdated } from 'vue'

const count = ref(0)

onBeforeUpdate(() => {
  // DOM still shows the old count
  console.log('before:', document.getElementById('out')?.textContent)
})

onUpdated(() => {
  // DOM now shows the new count
  console.log('after:', document.getElementById('out')?.textContent)
})
</script>

<template>
  <button @click="count++">+1</button>
  <p id="out">{{ count }}</p>
</template>

go deeper

for a junior

Recall that both hooks belong to re-renders, not the first render, and that onBeforeUpdate sees the old DOM while onUpdated sees the new one.

for a middle

Explain the order inside one update job: onBeforeUpdate, render, patch, then onUpdated as a post-flush callback, with a child updated by that patch running its onUpdated before the parent.

for a senior

Show you treat onUpdated as a per-render DOM hook, not a data listener, and reach for nextTick or a post-flush watcher when a specific change matters.

for a principal

Frame the hooks as a low-level escape hatch: code reviews should question every onUpdated because it couples logic to render frequency rather than to data.

## What the update phase is A Vue 3 component goes through three broad phases: **mount** (first render and DOM insertion), **update** (re-rendering after a change) and **unmount**. `onBeforeUpdate` and `onUpdated` are the two Composition API hooks of the update phase; their Options API equivalents are the `beforeUpdate` and `updated` options. Each component's render function runs inside a **render effect**: Vue records every reactive value the render reads (refs, reactive objects, props, computed values) and, when one of them changes, schedules that component to re-render. The re-render is the only thing that triggers these two hooks. The first render never does, which is why code that must run after both the first render and every later one usually needs `onMounted` as well. ## The order of one re-render When a tracked dependency changes, Vue queues the component's update job and runs it on the next flush. Inside that job, the order is fixed: 1. **`onBeforeUpdate` callbacks run.** No DOM has been touched yet. 2. The component's render function runs again and produces a new virtual DOM tree. 3. Vue **patches** the real DOM, applying only the differences. 4. **`onUpdated` callbacks are queued** as post-flush callbacks and run once the patching work of that flush is done. Children that this patch updates, for example because their props changed, are re-rendered inside the parent's patch, so in that case a **parent's `onUpdated` runs after the `onUpdated` of those children**, the same inside-out order as `onMounted`. ## What you can do in each hook | | `onBeforeUpdate` | `onUpdated` | |---|---|---| | DOM state | still the previous render | patched, shows the new state | | Mutating component state | safe: the render about to run reads it | risky: can schedule another render and loop | | Typical use | read old DOM values (scroll position, selection) before they change | DOM work that must follow every re-render | | Runs on first render | no | no | | Runs during SSR | no | no | Why is a state change safe in `onBeforeUpdate`? The hook runs inside the component's render effect before the render itself, and Vue turns off self-recursion for that moment. A mutation made there does not schedule a second render; the render that follows simply reads the new value. The Vue API reference states it directly: it is safe to modify component state inside this hook. `onUpdated` is the opposite case. By the time it runs, the render has finished; a state change the template reads schedules a brand-new render, which fires `onUpdated` again. The reference carries a warning not to mutate component state there. ## A typical pairing A useful pattern reads something in `onBeforeUpdate` and acts on it in `onUpdated`, touching only the DOM: ```ts import { onBeforeUpdate, onUpdated, useTemplateRef } from 'vue' const box = useTemplateRef<HTMLElement>('box') let prevHeight = 0 onBeforeUpdate(() => { prevHeight = box.value?.offsetHeight ?? 0 }) onUpdated(() => { const h = box.value?.offsetHeight ?? 0 if (h !== prevHeight) console.debug('height changed', prevHeight, '->', h) }) ``` Neither hook writes reactive state, so no extra render is triggered. `useTemplateRef` is a Vue 3.5 helper; on earlier 3.x a plain `ref(null)` whose name matches the template `ref` attribute plays the same role. ## A worked timeline Suppose a click handler runs `count.value++` and then `label.value = 'saved'`, and the template reads both. Neither assignment touches the DOM immediately. Vue queues the component's update job once, and on the next flush: 1. `onBeforeUpdate` runs; the DOM still shows the old count and the old label. 2. The render function runs once and reads both new values. 3. The patch updates the two text nodes. 4. `onUpdated` runs once; the DOM shows both new values. Two state changes, one re-render, one call of each hook. If the same handler also changed state that only a child component renders, the child gets its own, separate update job, and its hooks describe only that child's re-render. ## Limits worth stating in an interview - **They are not change listeners.** `onUpdated` does not say *which* state changed; several changes in the same tick are batched into one re-render and one hook call. - **Only the component's own re-render counts.** A change that only a child's template reads re-renders the child, not the parent, so the parent's hooks stay silent. - **Client only.** Neither hook runs during server-side rendering. - **Prefer targeted tools** when you need the DOM after a particular change: `await nextTick()` right after that change, or a watcher on that source with `flush: 'post'`. Knowing these limits is what separates using the hooks from misusing them: they are the right tool for DOM work that genuinely belongs to every re-render, and the wrong tool for reacting to data.

  • If you change a ref inside onBeforeUpdate, does Vue render twice?
    No. `onBeforeUpdate` runs inside the component's render effect before the render function, and Vue disables self-recursion for that step, so the change does not queue a second render. The render that follows reads the new value, and the DOM reflects it in the same pass. That is why the API reference calls state changes in this hook safe.
  • In what order do a parent's and a child's onUpdated run when the parent's re-render passes the child new props?
    The child's first. The child is re-rendered inside the parent's patch, so its `onUpdated` is queued before the parent's, and the parent's `onUpdated` runs last, once the whole subtree reflects the new state. It mirrors `onMounted`, which is also inside-out. A child that re-renders only because of its own state has a separate update and no such pairing.

saying these in an interview costs you the question

  • onUpdated also runs after the first render
  • onBeforeUpdate runs after the DOM has been patched
  • onUpdated tells you which piece of state changed
  • Any state change inside onBeforeUpdate triggers a second render
  • A parent's onUpdated always runs before its children's