Files
agent-skills/skills/vue-best-practices/reference/directive-vue2-migration-hooks.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

211 lines
5.4 KiB
Markdown

---
title: Vue 3 Directive Hooks Renamed from Vue 2
impact: HIGH
impactDescription: Using Vue 2 hook names in Vue 3 causes directives to silently fail
type: gotcha
tags: [vue3, vue2, migration, directives, hooks, breaking-change]
---
# Vue 3 Directive Hooks Renamed from Vue 2
**Impact: HIGH** - Vue 3 renamed all custom directive lifecycle hooks to align with component lifecycle hooks. Using Vue 2 hook names will cause your directives to silently fail since the hooks won't be called. Additionally, the `update` hook was removed entirely.
This is a breaking change that requires updating all custom directives when migrating from Vue 2 to Vue 3.
## Task Checklist
- [ ] Rename `bind` to `beforeMount`
- [ ] Rename `inserted` to `mounted`
- [ ] Replace `update` with `beforeUpdate` or `updated` (update was removed)
- [ ] Rename `componentUpdated` to `updated`
- [ ] Rename `unbind` to `unmounted`
- [ ] Add `beforeUpdate` if you need the old `update` behavior
## Hook Name Mapping
| Vue 2 | Vue 3 |
|-----------------|----------------|
| `bind` | `beforeMount` |
| `inserted` | `mounted` |
| `update` | **removed** |
| `componentUpdated` | `updated` |
| `unbind` | `unmounted` |
| (none) | `created` |
| (none) | `beforeUpdate` |
| (none) | `beforeUnmount`|
**Vue 2 (old):**
```javascript
// Vue 2 directive - WILL NOT WORK IN VUE 3
Vue.directive('demo', {
bind(el, binding, vnode) {
// Called when directive is first bound to element
},
inserted(el, binding, vnode) {
// Called when element is inserted into parent
},
update(el, binding, vnode, oldVnode) {
// Called on every VNode update (REMOVED in Vue 3)
},
componentUpdated(el, binding, vnode, oldVnode) {
// Called after component and children update
},
unbind(el, binding, vnode) {
// Called when directive is unbound from element
}
})
```
**Vue 3 (new):**
```javascript
// Vue 3 directive - Correct hook names
app.directive('demo', {
created(el, binding, vnode) {
// NEW: called before element's attributes or event listeners are applied
},
beforeMount(el, binding, vnode) {
// Was: bind
},
mounted(el, binding, vnode) {
// Was: inserted
},
beforeUpdate(el, binding, vnode, prevVnode) {
// NEW: called before the element itself is updated
},
updated(el, binding, vnode, prevVnode) {
// Was: componentUpdated
// Note: 'update' was removed - use this or beforeUpdate instead
},
beforeUnmount(el, binding, vnode) {
// NEW: called before element is unmounted
},
unmounted(el, binding, vnode) {
// Was: unbind
}
})
```
## Migration Examples
### Simple Focus Directive
```javascript
// Vue 2
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
// Vue 3
app.directive('focus', {
mounted(el) {
el.focus()
}
})
```
### Directive with Cleanup
```javascript
// Vue 2
Vue.directive('click-outside', {
bind(el, binding) {
el._handler = (e) => {
if (!el.contains(e.target)) binding.value(e)
}
document.addEventListener('click', el._handler)
},
unbind(el) {
document.removeEventListener('click', el._handler)
}
})
// Vue 3
app.directive('click-outside', {
beforeMount(el, binding) { // or mounted
el._handler = (e) => {
if (!el.contains(e.target)) binding.value(e)
}
document.addEventListener('click', el._handler)
},
unmounted(el) {
document.removeEventListener('click', el._handler)
}
})
```
### Directive with Updates
```javascript
// Vue 2 - using update hook
Vue.directive('color', {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
// Called on every VNode update
el.style.color = binding.value
}
})
// Vue 3 - update removed, use function shorthand or updated
app.directive('color', (el, binding) => {
// Function shorthand: called for both mounted AND updated
el.style.color = binding.value
})
// Or with object syntax
app.directive('color', {
mounted(el, binding) {
el.style.color = binding.value
},
updated(el, binding) {
// Use updated instead of update
el.style.color = binding.value
}
})
```
## Why `update` Was Removed
In Vue 2, `update` was called on every VNode update (before children updated), while `componentUpdated` was called after. The distinction was confusing and rarely needed. In Vue 3:
- `beforeUpdate` is called before the element updates
- `updated` is called after the element and all its children have updated
```javascript
// Vue 3 - if you need both before and after
app.directive('track-updates', {
beforeUpdate(el, binding) {
console.log('Before update, old value:', binding.oldValue)
},
updated(el, binding) {
console.log('After update, new value:', binding.value)
}
})
```
## vnode Structure Changes
In Vue 3, the `vnode` and `prevVnode` arguments also have different structure:
```javascript
// Vue 2
{
update(el, binding, vnode, oldVnode) {
// vnode.context was the component instance
console.log(vnode.context)
}
}
// Vue 3
{
updated(el, binding, vnode, prevVnode) {
// Use binding.instance instead of vnode.context
console.log(binding.instance)
}
}
```
## Reference
- [Vue 3 Migration Guide - Custom Directives](https://v3-migration.vuejs.org/breaking-changes/custom-directives)
- [Vue.js Custom Directives - Directive Hooks](https://vuejs.org/guide/reusability/custom-directives#directive-hooks)