--- title: Use withDirectives for Custom Directives in Render Functions impact: LOW impactDescription: Custom directives require special handling with withDirectives helper type: best-practice tags: [vue3, render-function, directives, custom-directive] --- # Use withDirectives for Custom Directives in Render Functions **Impact: LOW** - To apply custom directives to vnodes in render functions, use the `withDirectives` helper. Attempting to apply directives through props or other means will not work. The `withDirectives` function wraps a vnode and applies directives with their values, arguments, and modifiers. ## Task Checklist - [ ] Import `withDirectives` from 'vue' when using custom directives - [ ] Import or define the directive object - [ ] Pass directive as array: `[directive, value, argument, modifiers]` - [ ] Multiple directives can be applied at once **Incorrect:** ```javascript import { h } from 'vue' const vFocus = { mounted: (el) => el.focus() } export default { setup() { return () => { // WRONG: Directives don't work as props return h('input', { 'v-focus': true // This doesn't work }) } } } ``` **Correct:** ```javascript import { h, withDirectives } from 'vue' // Custom directive const vFocus = { mounted: (el) => el.focus() } export default { setup() { return () => { // CORRECT: Use withDirectives return withDirectives( h('input'), [[vFocus]] // Array of directive tuples ) } } } ``` ## Directive with Value, Argument, and Modifiers The directive tuple format: `[directive, value, argument, modifiers]` ```javascript import { h, withDirectives, resolveDirective } from 'vue' // Directive definition // Usage in template:
const vPin = { mounted(el, binding) { console.log(binding.value) // 200 console.log(binding.arg) // 'top' console.log(binding.modifiers) // { animate: true } el.style.position = 'fixed' el.style[binding.arg] = binding.value + 'px' } } export default { setup() { return () => withDirectives( h('div', 'Pinned content'), [ // [directive, value, argument, modifiers] [vPin, 200, 'top', { animate: true }] ] ) } } ``` ## Multiple Directives ```javascript import { h, withDirectives } from 'vue' const vFocus = { mounted: (el) => el.focus() } const vTooltip = { mounted(el, { value }) { el.title = value } } export default { setup() { return () => withDirectives( h('input', { placeholder: 'Enter text' }), [ [vFocus], [vTooltip, 'This is a tooltip'] ] ) } } ``` ## Using Registered Directives For globally or locally registered directives, use `resolveDirective`: ```javascript import { h, withDirectives, resolveDirective } from 'vue' // Assuming v-focus and v-tooltip are registered globally export default { setup() { return () => { const focus = resolveDirective('focus') const tooltip = resolveDirective('tooltip') return withDirectives( h('input'), [ [focus], [tooltip, 'Helpful tip'] ] ) } } } ``` ## Practical Example: Click Outside Directive ```javascript import { h, withDirectives, ref } from 'vue' const vClickOutside = { mounted(el, binding) { el._clickOutside = (event) => { if (!el.contains(event.target)) { binding.value(event) } } document.addEventListener('click', el._clickOutside) }, unmounted(el) { document.removeEventListener('click', el._clickOutside) } } export default { setup() { const isOpen = ref(true) const closeDropdown = () => { isOpen.value = false } return () => isOpen.value ? withDirectives( h('div', { class: 'dropdown' }, 'Dropdown content'), [[vClickOutside, closeDropdown]] ) : null } } ``` ## JSX with Directives Directives in JSX also require `withDirectives`: ```jsx import { withDirectives } from 'vue' const vFocus = { mounted: (el) => el.focus() } export default { setup() { return () => withDirectives( , [[vFocus]] ) } } ``` ## Reference - [Vue.js Render Functions - Custom Directives](https://vuejs.org/guide/extras/render-function.html#custom-directives) - [Vue.js Custom Directives](https://vuejs.org/guide/reusability/custom-directives.html)