@pythoughts/vue-skills-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/index.mjs +139 -0
- package/package.json +34 -0
- package/skills/create-adaptable-composable/SKILL.md +76 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +350 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +346 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-debug-guides/SKILL.md +205 -0
- package/skills/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
- package/skills/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
- package/skills/vue-debug-guides/reference/async-component-error-handling.md +115 -0
- package/skills/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
- package/skills/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
- package/skills/vue-debug-guides/reference/async-component-vue-router.md +109 -0
- package/skills/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
- package/skills/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
- package/skills/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
- package/skills/vue-debug-guides/reference/click-events-on-components.md +180 -0
- package/skills/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
- package/skills/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
- package/skills/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
- package/skills/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
- package/skills/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
- package/skills/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
- package/skills/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
- package/skills/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
- package/skills/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
- package/skills/vue-debug-guides/reference/computed-array-mutation.md +148 -0
- package/skills/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
- package/skills/vue-debug-guides/reference/computed-no-parameters.md +159 -0
- package/skills/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
- package/skills/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
- package/skills/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
- package/skills/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
- package/skills/vue-debug-guides/reference/define-expose-before-await.md +192 -0
- package/skills/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
- package/skills/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
- package/skills/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
- package/skills/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
- package/skills/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
- package/skills/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
- package/skills/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
- package/skills/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
- package/skills/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
- package/skills/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
- package/skills/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
- package/skills/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
- package/skills/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
- package/skills/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
- package/skills/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
- package/skills/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
- package/skills/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
- package/skills/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
- package/skills/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
- package/skills/vue-debug-guides/reference/mount-return-value.md +88 -0
- package/skills/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
- package/skills/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
- package/skills/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
- package/skills/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
- package/skills/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
- package/skills/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
- package/skills/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
- package/skills/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
- package/skills/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
- package/skills/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
- package/skills/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
- package/skills/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
- package/skills/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
- package/skills/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
- package/skills/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
- package/skills/vue-debug-guides/reference/reactive-destructuring.md +89 -0
- package/skills/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
- package/skills/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
- package/skills/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
- package/skills/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
- package/skills/vue-debug-guides/reference/ref-value-access.md +61 -0
- package/skills/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
- package/skills/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
- package/skills/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
- package/skills/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
- package/skills/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
- package/skills/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
- package/skills/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
- package/skills/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
- package/skills/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
- package/skills/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
- package/skills/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
- package/skills/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
- package/skills/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
- package/skills/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
- package/skills/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
- package/skills/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
- package/skills/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
- package/skills/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
- package/skills/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
- package/skills/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
- package/skills/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
- package/skills/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
- package/skills/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
- package/skills/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
- package/skills/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
- package/skills/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
- package/skills/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
- package/skills/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
- package/skills/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
- package/skills/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
- package/skills/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
- package/skills/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
- package/skills/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
- package/skills/vue-debug-guides/reference/transition-nested-duration.md +182 -0
- package/skills/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
- package/skills/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
- package/skills/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
- package/skills/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
- package/skills/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
- package/skills/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
- package/skills/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
- package/skills/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
- package/skills/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
- package/skills/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
- package/skills/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
- package/skills/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
- package/skills/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
- package/skills/vue-debug-guides/reference/v-for-component-props.md +95 -0
- package/skills/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
- package/skills/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
- package/skills/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
- package/skills/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
- package/skills/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
- package/skills/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
- package/skills/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
- package/skills/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
- package/skills/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
- package/skills/vue-debug-guides/reference/watch-flush-timing.md +189 -0
- package/skills/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
- package/skills/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
- package/skills/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
- package/skills/vue-jsx-best-practices/SKILL.md +12 -0
- package/skills/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
- package/skills/vue-options-api-best-practices/SKILL.md +23 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
- package/skills/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
- package/skills/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/vue-router-best-practices/SKILL.md +23 -0
- package/skills/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
- package/skills/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
- package/skills/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
- package/skills/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
- package/skills/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
- package/skills/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
- package/skills/vue-testing-best-practices/SKILL.md +29 -0
- package/skills/vue-testing-best-practices/reference/async-component-testing.md +163 -0
- package/skills/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
- package/skills/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
- package/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
- package/skills/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
- package/skills/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
- package/skills/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
- package/skills/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
- package/skills/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
- package/skills/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
- package/skills/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Template Functions Must Be Pure Without Side Effects
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Functions with side effects in templates cause unpredictable behavior on every re-render
|
|
5
|
+
type: efficiency
|
|
6
|
+
tags: [vue3, template, functions, performance, side-effects]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Template Functions Must Be Pure Without Side Effects
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Functions called in templates execute on every component re-render. Functions with side effects (modifying data, API calls, logging) will cause unpredictable behavior, performance issues, and difficult-to-debug bugs.
|
|
12
|
+
|
|
13
|
+
Template expressions including function calls are evaluated whenever the component updates. This makes them unsuitable for operations that should only happen once or that modify state.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Keep template functions pure (same input = same output)
|
|
18
|
+
- [ ] Never modify reactive state inside template functions
|
|
19
|
+
- [ ] Never make API calls or async operations in template functions
|
|
20
|
+
- [ ] Move side effects to event handlers, watchers, or lifecycle hooks
|
|
21
|
+
- [ ] Use computed properties for derived values instead of functions when possible
|
|
22
|
+
- [ ] Avoid expensive computations; use computed properties for caching
|
|
23
|
+
|
|
24
|
+
**Incorrect:**
|
|
25
|
+
```vue
|
|
26
|
+
<template>
|
|
27
|
+
<!-- BAD: Modifies state on every render -->
|
|
28
|
+
<p>{{ incrementAndGet() }}</p>
|
|
29
|
+
|
|
30
|
+
<!-- BAD: API call on every render -->
|
|
31
|
+
<div>{{ fetchUserName() }}</div>
|
|
32
|
+
|
|
33
|
+
<!-- BAD: Logging side effect -->
|
|
34
|
+
<span>{{ logAndFormat(date) }}</span>
|
|
35
|
+
|
|
36
|
+
<!-- BAD: Expensive computation without caching -->
|
|
37
|
+
<ul>
|
|
38
|
+
<li v-for="item in filterAndSort(items)" :key="item.id">
|
|
39
|
+
{{ item.name }}
|
|
40
|
+
</li>
|
|
41
|
+
</ul>
|
|
42
|
+
|
|
43
|
+
<!-- BAD: Random values change on every render -->
|
|
44
|
+
<p>{{ getRandomGreeting() }}</p>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup>
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
|
|
50
|
+
const count = ref(0)
|
|
51
|
+
const items = ref([/* large array */])
|
|
52
|
+
|
|
53
|
+
// BAD: Has side effect - modifies state
|
|
54
|
+
function incrementAndGet() {
|
|
55
|
+
count.value++ // Side effect!
|
|
56
|
+
return count.value
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// BAD: Async operation in template
|
|
60
|
+
async function fetchUserName() {
|
|
61
|
+
const res = await fetch('/api/user') // Side effect!
|
|
62
|
+
return (await res.json()).name
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// BAD: Logging is a side effect
|
|
66
|
+
function logAndFormat(date) {
|
|
67
|
+
console.log('Formatting date:', date) // Side effect!
|
|
68
|
+
return new Date(date).toLocaleDateString()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// BAD: Expensive, runs every render without caching
|
|
72
|
+
function filterAndSort(items) {
|
|
73
|
+
return items
|
|
74
|
+
.filter(i => i.active)
|
|
75
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// BAD: Non-deterministic
|
|
79
|
+
function getRandomGreeting() {
|
|
80
|
+
const greetings = ['Hello', 'Hi', 'Hey']
|
|
81
|
+
return greetings[Math.floor(Math.random() * greetings.length)]
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Correct:**
|
|
87
|
+
```vue
|
|
88
|
+
<template>
|
|
89
|
+
<!-- OK: Pure formatting function -->
|
|
90
|
+
<p>Count: {{ count }}</p>
|
|
91
|
+
<button @click="increment">Increment</button>
|
|
92
|
+
|
|
93
|
+
<!-- OK: Data fetched via lifecycle/watcher -->
|
|
94
|
+
<div>{{ userName }}</div>
|
|
95
|
+
|
|
96
|
+
<!-- OK: Pure function, no side effects -->
|
|
97
|
+
<span>{{ formatDate(date) }}</span>
|
|
98
|
+
|
|
99
|
+
<!-- OK: Computed property caches result -->
|
|
100
|
+
<ul>
|
|
101
|
+
<li v-for="item in filteredAndSortedItems" :key="item.id">
|
|
102
|
+
{{ item.name }}
|
|
103
|
+
</li>
|
|
104
|
+
</ul>
|
|
105
|
+
|
|
106
|
+
<!-- OK: Random value set once -->
|
|
107
|
+
<p>{{ greeting }}</p>
|
|
108
|
+
</template>
|
|
109
|
+
|
|
110
|
+
<script setup>
|
|
111
|
+
import { ref, computed, onMounted } from 'vue'
|
|
112
|
+
|
|
113
|
+
const count = ref(0)
|
|
114
|
+
const userName = ref('')
|
|
115
|
+
const date = ref(new Date())
|
|
116
|
+
const items = ref([/* large array */])
|
|
117
|
+
|
|
118
|
+
// Side effects in event handlers
|
|
119
|
+
function increment() {
|
|
120
|
+
count.value++
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fetch data in lifecycle hook
|
|
124
|
+
onMounted(async () => {
|
|
125
|
+
const res = await fetch('/api/user')
|
|
126
|
+
userName.value = (await res.json()).name
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Pure function - same input, same output
|
|
130
|
+
function formatDate(date) {
|
|
131
|
+
return new Date(date).toLocaleDateString()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Computed property - cached, only recalculates when dependencies change
|
|
135
|
+
const filteredAndSortedItems = computed(() => {
|
|
136
|
+
return items.value
|
|
137
|
+
.filter(i => i.active)
|
|
138
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Set random value once, not on every render
|
|
142
|
+
const greetings = ['Hello', 'Hi', 'Hey']
|
|
143
|
+
const greeting = ref(greetings[Math.floor(Math.random() * greetings.length)])
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Pure Function Guidelines
|
|
148
|
+
|
|
149
|
+
A pure function:
|
|
150
|
+
1. Given the same inputs, always returns the same output
|
|
151
|
+
2. Does not modify any external state
|
|
152
|
+
3. Does not perform I/O operations (network, console, file system)
|
|
153
|
+
4. Does not depend on mutable external state
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// PURE - safe for templates
|
|
157
|
+
function formatCurrency(amount, currency = 'USD') {
|
|
158
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function fullName(first, last) {
|
|
162
|
+
return `${first} ${last}`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isExpired(date) {
|
|
166
|
+
return new Date(date) < new Date()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// IMPURE - unsafe for templates
|
|
170
|
+
function logAndReturn(value) {
|
|
171
|
+
console.log(value) // I/O
|
|
172
|
+
return value
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getFromLocalStorage(key) {
|
|
176
|
+
return localStorage.getItem(key) // External state
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateAndReturn(obj, key, value) {
|
|
180
|
+
obj[key] = value // Mutation
|
|
181
|
+
return obj
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Reference
|
|
186
|
+
- [Vue.js Template Syntax - Calling Functions](https://vuejs.org/guide/essentials/template-syntax.html#calling-functions)
|
|
187
|
+
- [Vue.js Computed Properties](https://vuejs.org/guide/essentials/computed.html)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Template Refs Become Null When Elements Are Unmounted
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Refs become null when conditionally rendered elements are removed, causing errors if not handled
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, template-refs, v-if, watchers, conditional-rendering]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Template Refs Become Null When Elements Are Unmounted
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When using template refs with `v-if`, the ref becomes `null` when the element is unmounted. Watchers and effects that access these refs must handle the null case to avoid runtime errors.
|
|
12
|
+
|
|
13
|
+
This is especially tricky with `watchEffect` since it runs automatically and may execute when the ref is null.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always check for null before accessing ref properties when using v-if
|
|
18
|
+
- [ ] In watchers, explicitly handle the null case (element unmounted or not yet mounted)
|
|
19
|
+
- [ ] Consider whether v-show is more appropriate if you need persistent ref access
|
|
20
|
+
- [ ] Use optional chaining (?.) when accessing ref properties in uncertain contexts
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```vue
|
|
24
|
+
<script setup>
|
|
25
|
+
import { ref, watchEffect } from 'vue'
|
|
26
|
+
|
|
27
|
+
const inputEl = ref(null)
|
|
28
|
+
const showInput = ref(true)
|
|
29
|
+
|
|
30
|
+
// WRONG: No null check - will error when v-if is false
|
|
31
|
+
watchEffect(() => {
|
|
32
|
+
inputEl.value.focus() // TypeError when showInput is false
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<input v-if="showInput" ref="inputEl" />
|
|
38
|
+
<button @click="showInput = !showInput">Toggle</button>
|
|
39
|
+
</template>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Correct:**
|
|
43
|
+
```vue
|
|
44
|
+
<script setup>
|
|
45
|
+
import { ref, watchEffect } from 'vue'
|
|
46
|
+
|
|
47
|
+
const inputEl = ref(null)
|
|
48
|
+
const showInput = ref(true)
|
|
49
|
+
|
|
50
|
+
// CORRECT: Handle both mounted and unmounted states
|
|
51
|
+
watchEffect(() => {
|
|
52
|
+
if (inputEl.value) {
|
|
53
|
+
inputEl.value.focus()
|
|
54
|
+
} else {
|
|
55
|
+
// Element not mounted yet, or unmounted by v-if
|
|
56
|
+
console.log('Input element not available')
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<input v-if="showInput" ref="inputEl" />
|
|
63
|
+
<button @click="showInput = !showInput">Toggle</button>
|
|
64
|
+
</template>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```vue
|
|
68
|
+
<script setup>
|
|
69
|
+
import { ref, watch } from 'vue'
|
|
70
|
+
|
|
71
|
+
const inputEl = ref(null)
|
|
72
|
+
const showInput = ref(true)
|
|
73
|
+
|
|
74
|
+
// CORRECT: Watch the ref and handle null explicitly
|
|
75
|
+
watch(inputEl, (el) => {
|
|
76
|
+
if (el) {
|
|
77
|
+
el.focus()
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<input v-if="showInput" ref="inputEl" />
|
|
84
|
+
</template>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<script setup>
|
|
89
|
+
import { useTemplateRef, watchEffect } from 'vue'
|
|
90
|
+
|
|
91
|
+
// Vue 3.5+ approach
|
|
92
|
+
const input = useTemplateRef('my-input')
|
|
93
|
+
|
|
94
|
+
// CORRECT: Use optional chaining for safe access
|
|
95
|
+
watchEffect(() => {
|
|
96
|
+
input.value?.focus()
|
|
97
|
+
})
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<input v-if="showInput" ref="my-input" />
|
|
102
|
+
</template>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<script setup>
|
|
107
|
+
import { ref, onMounted } from 'vue'
|
|
108
|
+
|
|
109
|
+
const inputEl = ref(null)
|
|
110
|
+
const showInput = ref(true)
|
|
111
|
+
|
|
112
|
+
// ALTERNATIVE: Use v-show if you need consistent ref access
|
|
113
|
+
// v-show keeps element in DOM, just hides it with CSS
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<!-- Element always exists in DOM, ref is never null -->
|
|
118
|
+
<input v-show="showInput" ref="inputEl" />
|
|
119
|
+
</template>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Reference
|
|
123
|
+
- [Vue.js Template Refs](https://vuejs.org/guide/essentials/template-refs.html)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Template Ref Unwrapping Only Works for Top-Level Properties
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Nested refs in template expressions render as [object Object] instead of their values
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, reactivity, ref, template, unwrapping]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Template Ref Unwrapping Only Works for Top-Level Properties
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue only auto-unwraps refs that are top-level properties in the template render context. Nested refs (refs inside objects) are NOT unwrapped in expressions, causing `[object Object]` rendering or calculation errors.
|
|
12
|
+
|
|
13
|
+
This caveat trips up developers when they store refs inside reactive objects or plain objects and try to use them in template expressions like `{{ object.count + 1 }}`.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Keep refs at the top level of your setup return or script setup
|
|
18
|
+
- [ ] Destructure nested refs to top-level variables before using in expressions
|
|
19
|
+
- [ ] Be aware that text interpolation `{{ object.ref }}` DOES unwrap, but expressions `{{ object.ref + 1 }}` do NOT
|
|
20
|
+
- [ ] Consider restructuring data to avoid nested refs in templates
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```vue
|
|
24
|
+
<script setup>
|
|
25
|
+
import { ref } from 'vue'
|
|
26
|
+
|
|
27
|
+
const count = ref(0)
|
|
28
|
+
const object = { id: ref(1) }
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<!-- WRONG: Nested ref in expression - does NOT unwrap -->
|
|
33
|
+
<p>ID + 1 = {{ object.id + 1 }}</p>
|
|
34
|
+
<!-- Renders: "ID + 1 = [object Object]1" -->
|
|
35
|
+
|
|
36
|
+
<!-- Surprisingly, plain interpolation DOES work -->
|
|
37
|
+
<p>ID = {{ object.id }}</p>
|
|
38
|
+
<!-- Renders: "ID = 1" (unwrapped because it's the final expression) -->
|
|
39
|
+
</template>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Correct:**
|
|
43
|
+
```vue
|
|
44
|
+
<script setup>
|
|
45
|
+
import { ref } from 'vue'
|
|
46
|
+
|
|
47
|
+
const count = ref(0)
|
|
48
|
+
const object = { id: ref(1) }
|
|
49
|
+
|
|
50
|
+
// SOLUTION 1: Destructure to top-level
|
|
51
|
+
const { id } = object
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<!-- CORRECT: Top-level ref unwraps in all expressions -->
|
|
56
|
+
<p>Count + 1 = {{ count + 1 }}</p>
|
|
57
|
+
<!-- Renders: "Count + 1 = 1" -->
|
|
58
|
+
|
|
59
|
+
<!-- CORRECT: Destructured ref is now top-level -->
|
|
60
|
+
<p>ID + 1 = {{ id + 1 }}</p>
|
|
61
|
+
<!-- Renders: "ID + 1 = 2" -->
|
|
62
|
+
</template>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<script setup>
|
|
67
|
+
import { ref, computed } from 'vue'
|
|
68
|
+
|
|
69
|
+
const object = { id: ref(1) }
|
|
70
|
+
|
|
71
|
+
// SOLUTION 2: Use computed for derived values
|
|
72
|
+
const idPlusOne = computed(() => object.id.value + 1)
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<!-- CORRECT: Computed handles the .value access -->
|
|
77
|
+
<p>ID + 1 = {{ idPlusOne }}</p>
|
|
78
|
+
</template>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```vue
|
|
82
|
+
<script setup>
|
|
83
|
+
import { reactive } from 'vue'
|
|
84
|
+
|
|
85
|
+
// SOLUTION 3: Use reactive object instead (refs inside reactive auto-unwrap)
|
|
86
|
+
const object = reactive({ id: 1 })
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<!-- CORRECT: Plain reactive property works in expressions -->
|
|
91
|
+
<p>ID + 1 = {{ object.id + 1 }}</p>
|
|
92
|
+
</template>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
// WHY this happens:
|
|
97
|
+
// - Template compilation only adds .value to top-level identifiers
|
|
98
|
+
// - {{ count + 1 }} compiles to: count.value + 1
|
|
99
|
+
// - {{ object.id + 1 }} compiles to: object.id + 1 (no .value added!)
|
|
100
|
+
// - Plain {{ object.id }} has special handling for display purposes
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Reference
|
|
104
|
+
- [Vue.js Reactivity Fundamentals - Caveat when Unwrapping in Templates](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#caveat-when-unwrapping-in-templates)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Template Ref Array Order Not Guaranteed in v-for
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Refs collected from v-for may not match source array order, causing index-based bugs
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, template-refs, v-for, arrays, ordering]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Template Ref Array Order Not Guaranteed in v-for
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When using template refs inside `v-for`, Vue collects the element references into an array. However, this array does NOT guarantee the same order as the source array. Relying on index-based access can lead to subtle bugs.
|
|
12
|
+
|
|
13
|
+
This caveat is not obvious and can cause hard-to-debug issues when you assume the ref array matches your data order.
|
|
14
|
+
|
|
15
|
+
> **Warning: `useTemplateRef()` does NOT work with v-for refs in Vue 3.5**
|
|
16
|
+
>
|
|
17
|
+
> The `useTemplateRef()` API returns `null` when used with refs inside `v-for`. This is a known limitation. You must use the legacy pattern with `ref()` and a matching template ref name:
|
|
18
|
+
>
|
|
19
|
+
> ```ts
|
|
20
|
+
> // Does NOT work with v-for - returns null
|
|
21
|
+
> const itemRefs = useTemplateRef('items')
|
|
22
|
+
>
|
|
23
|
+
> // Works with v-for - use this pattern instead
|
|
24
|
+
> const items = ref([]) // name must match ref="items" in template
|
|
25
|
+
> ```
|
|
26
|
+
>
|
|
27
|
+
> The examples in this rule show `useTemplateRef()` for illustration, but in practice you should use the legacy `ref()` pattern for v-for scenarios until this limitation is addressed.
|
|
28
|
+
|
|
29
|
+
## Task Checklist
|
|
30
|
+
|
|
31
|
+
- [ ] Never assume ref array indices match source data array indices
|
|
32
|
+
- [ ] Use data attributes or other identifiers to correlate refs with data
|
|
33
|
+
- [ ] Consider function refs for complex scenarios requiring ordered access
|
|
34
|
+
- [ ] Test with dynamic list operations (add, remove, reorder) to verify behavior
|
|
35
|
+
|
|
36
|
+
**Incorrect:**
|
|
37
|
+
```vue
|
|
38
|
+
<script setup>
|
|
39
|
+
import { ref, useTemplateRef, onMounted } from 'vue'
|
|
40
|
+
|
|
41
|
+
const items = ref(['First', 'Second', 'Third'])
|
|
42
|
+
const itemRefs = useTemplateRef('items')
|
|
43
|
+
|
|
44
|
+
onMounted(() => {
|
|
45
|
+
// WRONG: Assuming itemRefs[0] corresponds to items[0]
|
|
46
|
+
// The order is NOT guaranteed to match!
|
|
47
|
+
items.value.forEach((item, index) => {
|
|
48
|
+
console.log(`${item}: `, itemRefs.value[index]) // May be wrong element!
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<ul>
|
|
55
|
+
<li v-for="item in items" ref="items" :key="item">
|
|
56
|
+
{{ item }}
|
|
57
|
+
</li>
|
|
58
|
+
</ul>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Correct:**
|
|
63
|
+
```vue
|
|
64
|
+
<script setup>
|
|
65
|
+
import { ref, useTemplateRef, onMounted } from 'vue'
|
|
66
|
+
|
|
67
|
+
const items = ref([
|
|
68
|
+
{ id: 1, text: 'First' },
|
|
69
|
+
{ id: 2, text: 'Second' },
|
|
70
|
+
{ id: 3, text: 'Third' }
|
|
71
|
+
])
|
|
72
|
+
const itemRefs = useTemplateRef('items')
|
|
73
|
+
|
|
74
|
+
onMounted(() => {
|
|
75
|
+
// CORRECT: Use data attributes to identify elements
|
|
76
|
+
itemRefs.value.forEach(el => {
|
|
77
|
+
const id = el.dataset.id
|
|
78
|
+
const item = items.value.find(i => i.id === Number(id))
|
|
79
|
+
console.log(`${item.text}: `, el)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<ul>
|
|
86
|
+
<li
|
|
87
|
+
v-for="item in items"
|
|
88
|
+
ref="items"
|
|
89
|
+
:key="item.id"
|
|
90
|
+
:data-id="item.id"
|
|
91
|
+
>
|
|
92
|
+
{{ item.text }}
|
|
93
|
+
</li>
|
|
94
|
+
</ul>
|
|
95
|
+
</template>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<script setup>
|
|
100
|
+
import { ref, onMounted, onBeforeUpdate } from 'vue'
|
|
101
|
+
|
|
102
|
+
const items = ref(['First', 'Second', 'Third'])
|
|
103
|
+
const itemRefs = ref(new Map())
|
|
104
|
+
|
|
105
|
+
// CORRECT: Use function refs for precise control
|
|
106
|
+
function setItemRef(el, item) {
|
|
107
|
+
if (el) {
|
|
108
|
+
itemRefs.value.set(item, el)
|
|
109
|
+
} else {
|
|
110
|
+
itemRefs.value.delete(item)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Reset before each update to handle removed items
|
|
115
|
+
onBeforeUpdate(() => {
|
|
116
|
+
itemRefs.value.clear()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
onMounted(() => {
|
|
120
|
+
// Access refs by their associated data item
|
|
121
|
+
items.value.forEach(item => {
|
|
122
|
+
const el = itemRefs.value.get(item)
|
|
123
|
+
console.log(`${item}: `, el)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<ul>
|
|
130
|
+
<li
|
|
131
|
+
v-for="item in items"
|
|
132
|
+
:key="item"
|
|
133
|
+
:ref="(el) => setItemRef(el, item)"
|
|
134
|
+
>
|
|
135
|
+
{{ item }}
|
|
136
|
+
</li>
|
|
137
|
+
</ul>
|
|
138
|
+
</template>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```vue
|
|
142
|
+
<script setup>
|
|
143
|
+
import { ref, useTemplateRef, onMounted } from 'vue'
|
|
144
|
+
|
|
145
|
+
const items = ref(['First', 'Second', 'Third'])
|
|
146
|
+
const itemRefs = useTemplateRef('items')
|
|
147
|
+
|
|
148
|
+
// CORRECT: If order matters, sort refs by DOM position
|
|
149
|
+
onMounted(() => {
|
|
150
|
+
const sortedRefs = [...itemRefs.value].sort((a, b) => {
|
|
151
|
+
// Sort by DOM order using compareDocumentPosition
|
|
152
|
+
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Now sortedRefs matches visual/DOM order
|
|
156
|
+
sortedRefs.forEach((el, index) => {
|
|
157
|
+
console.log(`Position ${index}: `, el.textContent)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<template>
|
|
163
|
+
<ul>
|
|
164
|
+
<li v-for="item in items" ref="items" :key="item">
|
|
165
|
+
{{ item }}
|
|
166
|
+
</li>
|
|
167
|
+
</ul>
|
|
168
|
+
</template>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Reference
|
|
172
|
+
- [Vue.js Template Refs - Refs inside v-for](https://vuejs.org/guide/essentials/template-refs.html#refs-inside-v-for)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Textarea Interpolation is One-Way Only - Use v-model for Two-Way Binding
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Using {{ text }} inside textarea displays initial value but user input does NOT update the ref
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, v-model, forms, textarea, interpolation, template]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Textarea Interpolation is One-Way Only - Use v-model for Two-Way Binding
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Interpolation in textarea (`{{ text }}`) provides one-way binding only - it displays the initial value but user input does NOT update the ref. This creates a confusing disconnect where the textarea shows content but edits are silently lost.
|
|
12
|
+
|
|
13
|
+
Unlike v-model which provides two-way binding, interpolation only renders the initial ref value into the textarea. When users type, the ref remains unchanged, making form submissions return stale data. Always use v-model for two-way binding in textareas.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never use interpolation inside textarea tags
|
|
18
|
+
- [ ] Always use v-model for textarea two-way binding
|
|
19
|
+
- [ ] Search codebase for `<textarea>{{` patterns that may be silently broken
|
|
20
|
+
- [ ] Add linting rules to catch this pattern if possible
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```html
|
|
24
|
+
<script setup>
|
|
25
|
+
import { ref } from 'vue'
|
|
26
|
+
|
|
27
|
+
const message = ref('Hello World')
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<!-- WRONG: One-way binding only! Shows initial value but edits don't update ref -->
|
|
32
|
+
<textarea>{{ message }}</textarea>
|
|
33
|
+
|
|
34
|
+
<!-- Also WRONG: User can type but changes are lost -->
|
|
35
|
+
<textarea>{{ userBio }}</textarea>
|
|
36
|
+
|
|
37
|
+
<!-- The textarea displays content but ref never updates -->
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Correct:**
|
|
42
|
+
```html
|
|
43
|
+
<script setup>
|
|
44
|
+
import { ref } from 'vue'
|
|
45
|
+
|
|
46
|
+
const message = ref('Hello World')
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<!-- CORRECT: Use v-model for textarea -->
|
|
51
|
+
<textarea v-model="message"></textarea>
|
|
52
|
+
|
|
53
|
+
<!-- For read-only display, still use v-model or :value -->
|
|
54
|
+
<textarea v-model="message" readonly></textarea>
|
|
55
|
+
|
|
56
|
+
<!-- Or one-way binding with :value -->
|
|
57
|
+
<textarea :value="message" readonly></textarea>
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<!-- With placeholder and other attributes -->
|
|
63
|
+
<textarea
|
|
64
|
+
v-model="message"
|
|
65
|
+
placeholder="Enter your message..."
|
|
66
|
+
rows="5"
|
|
67
|
+
maxlength="500"
|
|
68
|
+
></textarea>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Reference
|
|
72
|
+
- [Vue.js Form Input Bindings - Multiline text](https://vuejs.org/guide/essentials/forms.html#multiline-text)
|