@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,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: v-model.number Uses parseFloat Not valueAsNumber
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: .number modifier returns empty string for empty input and uses parseFloat, not native valueAsNumber
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, v-model, forms, input, number, type-coercion, modifiers]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# v-model.number Uses parseFloat Not valueAsNumber
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - The `.number` modifier doesn't behave like the native `valueAsNumber` property. It returns an empty string (not NaN) for empty inputs, and uses `parseFloat()` which has different parsing rules. This can cause unexpected type issues in calculations and validations.
|
|
12
|
+
|
|
13
|
+
Understanding these differences is crucial when working with numeric forms, especially for calculations, min/max validation, or when interfacing with APIs that expect strict number types.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Expect empty string (not 0 or NaN) when input is cleared with `.number` modifier
|
|
18
|
+
- [ ] Handle the empty string case in your validation and calculations
|
|
19
|
+
- [ ] Remember `.number` uses parseFloat - "123abc" becomes 123, not NaN
|
|
20
|
+
- [ ] For strict numeric parsing, add custom validation
|
|
21
|
+
|
|
22
|
+
**Key Differences:**
|
|
23
|
+
|
|
24
|
+
| Scenario | `.number` modifier | Native `valueAsNumber` |
|
|
25
|
+
|----------|-------------------|----------------------|
|
|
26
|
+
| Empty input | `''` (empty string) | `NaN` |
|
|
27
|
+
| `"123"` | `123` | `123` |
|
|
28
|
+
| `"123.45"` | `123.45` | `123.45` |
|
|
29
|
+
| `"123abc"` | `123` | `NaN` |
|
|
30
|
+
| `"abc"` | `'abc'` (original string) | `NaN` |
|
|
31
|
+
|
|
32
|
+
**Problem - Unexpected types:**
|
|
33
|
+
```html
|
|
34
|
+
<script setup>
|
|
35
|
+
import { ref, computed } from 'vue'
|
|
36
|
+
|
|
37
|
+
const price = ref(0)
|
|
38
|
+
const quantity = ref(1)
|
|
39
|
+
|
|
40
|
+
const total = computed(() => {
|
|
41
|
+
// PROBLEM: price might be '' (empty string) when input is cleared
|
|
42
|
+
return price.value * quantity.value // '' * 1 = 0, but typeof is still number
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
function validatePrice() {
|
|
46
|
+
// PROBLEM: This check fails when input is empty
|
|
47
|
+
if (typeof price.value !== 'number') {
|
|
48
|
+
// Never enters here! '' is still treated as "processed"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// PROBLEM: parseFloat("$100") returns NaN, but "100abc" returns 100
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<input v-model.number="price" type="number">
|
|
57
|
+
<!-- When user clears input, price.value becomes '' not 0 or NaN -->
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Solution - Handle empty string explicitly:**
|
|
62
|
+
```html
|
|
63
|
+
<script setup>
|
|
64
|
+
import { ref, computed } from 'vue'
|
|
65
|
+
|
|
66
|
+
const price = ref(0)
|
|
67
|
+
const quantity = ref(1)
|
|
68
|
+
|
|
69
|
+
const total = computed(() => {
|
|
70
|
+
// CORRECT: Handle empty string case
|
|
71
|
+
const priceNum = price.value === '' ? 0 : price.value
|
|
72
|
+
return priceNum * quantity.value
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Or use a wrapper computed for safer access
|
|
76
|
+
const safePrice = computed(() => {
|
|
77
|
+
if (price.value === '' || price.value === null) return 0
|
|
78
|
+
return Number(price.value) || 0
|
|
79
|
+
})
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<input v-model.number="price" type="number" min="0">
|
|
84
|
+
<p>Total: ${{ total.toFixed(2) }}</p>
|
|
85
|
+
</template>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Solution - Custom input handling for strict parsing:**
|
|
89
|
+
```html
|
|
90
|
+
<script setup>
|
|
91
|
+
import { ref } from 'vue'
|
|
92
|
+
|
|
93
|
+
const price = ref(0)
|
|
94
|
+
|
|
95
|
+
function handlePriceInput(event) {
|
|
96
|
+
const value = event.target.value
|
|
97
|
+
|
|
98
|
+
// Strict parsing - only accept valid numbers
|
|
99
|
+
const parsed = parseFloat(value)
|
|
100
|
+
|
|
101
|
+
if (value === '') {
|
|
102
|
+
price.value = 0 // Or null, depending on your needs
|
|
103
|
+
} else if (!isNaN(parsed) && isFinite(parsed)) {
|
|
104
|
+
// Additional check: ensure entire string is numeric
|
|
105
|
+
if (/^-?\d*\.?\d+$/.test(value.trim())) {
|
|
106
|
+
price.value = parsed
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Invalid input - keep previous value
|
|
110
|
+
}
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<!-- Manual binding for strict numeric control -->
|
|
115
|
+
<input
|
|
116
|
+
:value="price"
|
|
117
|
+
@input="handlePriceInput"
|
|
118
|
+
type="number"
|
|
119
|
+
>
|
|
120
|
+
</template>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Reference
|
|
124
|
+
- [Vue.js Form Input Bindings - .number](https://vuejs.org/guide/essentials/forms.html#number)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: v-show Does Not Work on template or With v-else
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Using v-show on template silently fails, element is always rendered
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, conditional-rendering, v-show, template, limitations]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# v-show Does Not Work on template or With v-else
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - `v-show` cannot be used on `<template>` elements because templates don't render to the DOM (so there's nothing to apply `display: none` to). Additionally, `v-show` does not support `v-else`. Using these incorrectly results in elements that are always visible or else branches that never work.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Never use v-show on `<template>` elements - use v-if instead
|
|
16
|
+
- [ ] Never use v-else with v-show - use separate v-show with negated condition
|
|
17
|
+
- [ ] Remember v-show only works on actual DOM elements
|
|
18
|
+
- [ ] If you need to toggle multiple elements frequently, wrap in a real element (div, span)
|
|
19
|
+
|
|
20
|
+
**Incorrect:**
|
|
21
|
+
```html
|
|
22
|
+
<!-- WRONG: v-show on <template> - silently does nothing -->
|
|
23
|
+
<template>
|
|
24
|
+
<template v-show="isVisible">
|
|
25
|
+
<h1>Title</h1>
|
|
26
|
+
<p>Content</p>
|
|
27
|
+
</template>
|
|
28
|
+
<!-- These elements will ALWAYS be visible -->
|
|
29
|
+
</template>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<!-- WRONG: v-else with v-show - v-else is not supported -->
|
|
34
|
+
<template>
|
|
35
|
+
<div v-show="isLoggedIn">Welcome!</div>
|
|
36
|
+
<div v-else>Please log in</div> <!-- This v-else won't work -->
|
|
37
|
+
</template>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<!-- WRONG: Mixing v-show and v-else expectations -->
|
|
42
|
+
<template>
|
|
43
|
+
<span v-show="status === 'success'">Success!</span>
|
|
44
|
+
<span v-else-if="status === 'error'">Error</span> <!-- Not supported -->
|
|
45
|
+
</template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Correct:**
|
|
49
|
+
```html
|
|
50
|
+
<!-- CORRECT: Use v-if on <template> for multiple elements -->
|
|
51
|
+
<template>
|
|
52
|
+
<template v-if="isVisible">
|
|
53
|
+
<h1>Title</h1>
|
|
54
|
+
<p>Content</p>
|
|
55
|
+
</template>
|
|
56
|
+
</template>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<!-- CORRECT: Use negated v-show for "else" behavior -->
|
|
61
|
+
<template>
|
|
62
|
+
<div v-show="isLoggedIn">Welcome!</div>
|
|
63
|
+
<div v-show="!isLoggedIn">Please log in</div>
|
|
64
|
+
</template>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<!-- CORRECT: Wrap in a real element if you need v-show for multiple elements -->
|
|
69
|
+
<template>
|
|
70
|
+
<div v-show="isVisible">
|
|
71
|
+
<h1>Title</h1>
|
|
72
|
+
<p>Content</p>
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<!-- CORRECT: Use v-if/v-else when you need else branches -->
|
|
79
|
+
<template>
|
|
80
|
+
<div v-if="status === 'success'">Success!</div>
|
|
81
|
+
<div v-else-if="status === 'error'">Error</div>
|
|
82
|
+
<div v-else>Loading...</div>
|
|
83
|
+
</template>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<!-- CORRECT: Multiple v-show conditions -->
|
|
88
|
+
<template>
|
|
89
|
+
<span v-show="status === 'success'">Success!</span>
|
|
90
|
+
<span v-show="status === 'error'">Error</span>
|
|
91
|
+
<span v-show="status === 'loading'">Loading...</span>
|
|
92
|
+
</template>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Why This Limitation Exists
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// v-show works by toggling the CSS display property
|
|
99
|
+
// This requires an actual DOM element
|
|
100
|
+
|
|
101
|
+
// <template> is a virtual element - it doesn't render to DOM
|
|
102
|
+
// It's just a wrapper for Vue's rendering logic
|
|
103
|
+
|
|
104
|
+
// After compilation:
|
|
105
|
+
// <template v-if="show"><p>Hi</p></template>
|
|
106
|
+
// Renders as: <p>Hi</p> (when show is true)
|
|
107
|
+
// Template itself is gone
|
|
108
|
+
|
|
109
|
+
// v-show needs a real element to set display: none on
|
|
110
|
+
// Since <template> doesn't exist in DOM, v-show has nothing to work with
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## When to Choose Each
|
|
114
|
+
|
|
115
|
+
| Need | Use |
|
|
116
|
+
|------|-----|
|
|
117
|
+
| Toggle multiple elements with CSS | Wrap in real element + `v-show` |
|
|
118
|
+
| Toggle multiple elements without wrapper | `<template v-if>` |
|
|
119
|
+
| Need v-else branches | `v-if`/`v-else` |
|
|
120
|
+
| Frequent toggle, single element | `v-show` |
|
|
121
|
+
| Frequent toggle, need "else" | Two `v-show` with negated conditions |
|
|
122
|
+
|
|
123
|
+
## Reference
|
|
124
|
+
- [Vue.js Conditional Rendering - v-show](https://vuejs.org/guide/essentials/conditional.html#v-show)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Clean Up Async Operations in Watchers to Prevent Race Conditions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Stale async requests can overwrite newer data, causing incorrect UI state and hard-to-debug issues
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, watch, watchers, async, cleanup, race-condition, abort]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Clean Up Async Operations in Watchers to Prevent Race Conditions
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - When a watched value changes rapidly, multiple async operations run concurrently. Without cleanup, a slow earlier request can complete after a faster later request, overwriting current data with stale results.
|
|
12
|
+
|
|
13
|
+
Always use `onWatcherCleanup` or the `onCleanup` callback parameter to cancel pending async operations when the watcher re-runs or the component unmounts.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Use `onWatcherCleanup()` or `onCleanup` parameter in async watchers
|
|
18
|
+
- [ ] Use `AbortController` to cancel pending fetch requests
|
|
19
|
+
- [ ] Cancel any setTimeout/setInterval calls in cleanup
|
|
20
|
+
- [ ] Invalidate previous async operation results with flags
|
|
21
|
+
- [ ] Consider debouncing rapid changes before fetching
|
|
22
|
+
|
|
23
|
+
**Incorrect:**
|
|
24
|
+
```javascript
|
|
25
|
+
import { ref, watch } from 'vue'
|
|
26
|
+
|
|
27
|
+
const searchQuery = ref('')
|
|
28
|
+
const results = ref([])
|
|
29
|
+
|
|
30
|
+
// BAD: Race condition - slow request for "a" can finish after fast request for "ab"
|
|
31
|
+
watch(searchQuery, async (query) => {
|
|
32
|
+
if (query) {
|
|
33
|
+
const response = await fetch(`/api/search?q=${query}`)
|
|
34
|
+
results.value = await response.json() // May overwrite newer results!
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// BAD: No cleanup for timeouts
|
|
39
|
+
watch(searchQuery, (query) => {
|
|
40
|
+
// Previous timeout keeps running even when query changes
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
performExpensiveSearch(query)
|
|
43
|
+
}, 500)
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Correct:**
|
|
48
|
+
```javascript
|
|
49
|
+
import { ref, watch, onWatcherCleanup } from 'vue'
|
|
50
|
+
|
|
51
|
+
const searchQuery = ref('')
|
|
52
|
+
const results = ref([])
|
|
53
|
+
const loading = ref(false)
|
|
54
|
+
|
|
55
|
+
// CORRECT: Using onWatcherCleanup (Vue 3.5+)
|
|
56
|
+
watch(searchQuery, async (query) => {
|
|
57
|
+
if (!query) {
|
|
58
|
+
results.value = []
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const controller = new AbortController()
|
|
63
|
+
|
|
64
|
+
// Register cleanup to abort on re-run or unmount
|
|
65
|
+
onWatcherCleanup(() => {
|
|
66
|
+
controller.abort()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
loading.value = true
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch(`/api/search?q=${query}`, {
|
|
72
|
+
signal: controller.signal
|
|
73
|
+
})
|
|
74
|
+
results.value = await response.json()
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err.name !== 'AbortError') {
|
|
77
|
+
console.error('Search failed:', err)
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
loading.value = false
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Using onCleanup Parameter
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
import { ref, watch } from 'vue'
|
|
89
|
+
|
|
90
|
+
const userId = ref(1)
|
|
91
|
+
const userData = ref(null)
|
|
92
|
+
|
|
93
|
+
// CORRECT: Using onCleanup callback parameter
|
|
94
|
+
watch(userId, (newId, oldId, onCleanup) => {
|
|
95
|
+
const controller = new AbortController()
|
|
96
|
+
|
|
97
|
+
fetch(`/api/users/${newId}`, { signal: controller.signal })
|
|
98
|
+
.then(res => res.json())
|
|
99
|
+
.then(data => {
|
|
100
|
+
userData.value = data
|
|
101
|
+
})
|
|
102
|
+
.catch(err => {
|
|
103
|
+
if (err.name !== 'AbortError') {
|
|
104
|
+
console.error(err)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
onCleanup(() => {
|
|
109
|
+
controller.abort()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Cleanup with Timeouts
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
import { ref, watch, onWatcherCleanup } from 'vue'
|
|
118
|
+
|
|
119
|
+
const input = ref('')
|
|
120
|
+
|
|
121
|
+
// CORRECT: Cancel previous timeout on new input
|
|
122
|
+
watch(input, (value) => {
|
|
123
|
+
const timeoutId = setTimeout(() => {
|
|
124
|
+
performExpensiveOperation(value)
|
|
125
|
+
}, 300)
|
|
126
|
+
|
|
127
|
+
onWatcherCleanup(() => {
|
|
128
|
+
clearTimeout(timeoutId)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Invalidation Flag Pattern
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
import { ref, watch } from 'vue'
|
|
137
|
+
|
|
138
|
+
const id = ref(1)
|
|
139
|
+
const data = ref(null)
|
|
140
|
+
|
|
141
|
+
// CORRECT: Invalidation flag for non-abortable operations
|
|
142
|
+
watch(id, async (newId, oldId, onCleanup) => {
|
|
143
|
+
let cancelled = false
|
|
144
|
+
|
|
145
|
+
onCleanup(() => {
|
|
146
|
+
cancelled = true
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const result = await someNonAbortableAsyncOperation(newId)
|
|
150
|
+
|
|
151
|
+
// Check if this watch run is still valid
|
|
152
|
+
if (!cancelled) {
|
|
153
|
+
data.value = result
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## watchEffect Cleanup
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
import { ref, watchEffect, onWatcherCleanup } from 'vue'
|
|
162
|
+
|
|
163
|
+
const resourceId = ref('abc')
|
|
164
|
+
|
|
165
|
+
watchEffect(async () => {
|
|
166
|
+
const id = resourceId.value
|
|
167
|
+
const controller = new AbortController()
|
|
168
|
+
|
|
169
|
+
onWatcherCleanup(() => {
|
|
170
|
+
controller.abort()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const data = await fetchResource(id, { signal: controller.signal })
|
|
174
|
+
processData(data)
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Reference
|
|
179
|
+
- [Vue.js Watchers - Callback Flush Timing](https://vuejs.org/guide/essentials/watchers.html#callback-flush-timing)
|
|
180
|
+
- [Vue.js Watchers - Side Effect Cleanup](https://vuejs.org/api/reactivity-core.html#watcheffect)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Watchers Created Asynchronously Must Be Manually Stopped
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Async-created watchers are not bound to component lifecycle and cause memory leaks
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, watch, watchers, async, memory-leak, lifecycle, cleanup]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Watchers Created Asynchronously Must Be Manually Stopped
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Watchers created inside async callbacks (setTimeout, Promise.then, async/await) are not automatically bound to the component instance. They continue running after the component unmounts, causing memory leaks and errors.
|
|
12
|
+
|
|
13
|
+
Always manually stop watchers that are created asynchronously, or restructure your code to create watchers synchronously with conditional logic.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Create watchers synchronously in setup() or lifecycle hooks when possible
|
|
18
|
+
- [ ] If async creation is unavoidable, store and call the unwatch function
|
|
19
|
+
- [ ] Use `onUnmounted` to clean up async-created watchers
|
|
20
|
+
- [ ] Consider using conditional logic inside a sync watcher instead
|
|
21
|
+
- [ ] Watch for this pattern in setTimeout, Promise callbacks, and after await
|
|
22
|
+
|
|
23
|
+
**Incorrect:**
|
|
24
|
+
```vue
|
|
25
|
+
<script setup>
|
|
26
|
+
import { ref, watch, watchEffect, onMounted } from 'vue'
|
|
27
|
+
|
|
28
|
+
const data = ref(null)
|
|
29
|
+
|
|
30
|
+
// BAD: Watcher created in setTimeout won't auto-stop
|
|
31
|
+
onMounted(() => {
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
watchEffect(() => {
|
|
34
|
+
console.log(data.value) // Keeps running after unmount!
|
|
35
|
+
})
|
|
36
|
+
}, 1000)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// BAD: Watcher created after await won't auto-stop
|
|
40
|
+
onMounted(async () => {
|
|
41
|
+
await loadInitialData()
|
|
42
|
+
|
|
43
|
+
// This watcher is NOT bound to component lifecycle
|
|
44
|
+
watch(data, (newVal) => {
|
|
45
|
+
processData(newVal) // Memory leak!
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// BAD: Watcher in Promise callback
|
|
50
|
+
fetch('/api/config').then(() => {
|
|
51
|
+
watch(data, () => {
|
|
52
|
+
// Leaks memory!
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Correct:**
|
|
59
|
+
```vue
|
|
60
|
+
<script setup>
|
|
61
|
+
import { ref, watch, watchEffect, onMounted, onUnmounted } from 'vue'
|
|
62
|
+
|
|
63
|
+
const data = ref(null)
|
|
64
|
+
const isDataLoaded = ref(false)
|
|
65
|
+
let asyncWatcherCleanup = null
|
|
66
|
+
|
|
67
|
+
// CORRECT: Synchronous watcher with conditional logic
|
|
68
|
+
watch(
|
|
69
|
+
data,
|
|
70
|
+
(newVal) => {
|
|
71
|
+
if (isDataLoaded.value && newVal) {
|
|
72
|
+
processData(newVal)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
onMounted(async () => {
|
|
78
|
+
await loadInitialData()
|
|
79
|
+
isDataLoaded.value = true
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// CORRECT: Manual cleanup for async-created watcher
|
|
83
|
+
onMounted(() => {
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
const unwatch = watchEffect(() => {
|
|
86
|
+
console.log(data.value)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Store for cleanup
|
|
90
|
+
asyncWatcherCleanup = unwatch
|
|
91
|
+
}, 1000)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
onUnmounted(() => {
|
|
95
|
+
// Clean up async watcher
|
|
96
|
+
if (asyncWatcherCleanup) {
|
|
97
|
+
asyncWatcherCleanup()
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Preferred Pattern: Conditional Watch Logic
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<script setup>
|
|
107
|
+
import { ref, watch, onMounted } from 'vue'
|
|
108
|
+
|
|
109
|
+
const config = ref(null)
|
|
110
|
+
const userData = ref(null)
|
|
111
|
+
|
|
112
|
+
// BEST: Create watcher synchronously, handle async condition inside
|
|
113
|
+
watch(
|
|
114
|
+
userData,
|
|
115
|
+
(newData) => {
|
|
116
|
+
// Only process when config is loaded
|
|
117
|
+
if (config.value && newData) {
|
|
118
|
+
applyUserSettings(config.value, newData)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
onMounted(async () => {
|
|
124
|
+
config.value = await fetchConfig()
|
|
125
|
+
// Watcher will start processing once config is loaded
|
|
126
|
+
})
|
|
127
|
+
</script>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Using watchEffect with Conditional Logic
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<script setup>
|
|
134
|
+
import { ref, watchEffect, onMounted } from 'vue'
|
|
135
|
+
|
|
136
|
+
const apiData = ref(null)
|
|
137
|
+
const isReady = ref(false)
|
|
138
|
+
|
|
139
|
+
// GOOD: Synchronous watchEffect with condition
|
|
140
|
+
watchEffect(() => {
|
|
141
|
+
if (isReady.value && apiData.value) {
|
|
142
|
+
// This pattern avoids async watcher creation
|
|
143
|
+
doSomethingWithData(apiData.value)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
onMounted(async () => {
|
|
148
|
+
apiData.value = await fetchData()
|
|
149
|
+
isReady.value = true
|
|
150
|
+
})
|
|
151
|
+
</script>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Tracking Multiple Async Watchers
|
|
155
|
+
|
|
156
|
+
```vue
|
|
157
|
+
<script setup>
|
|
158
|
+
import { ref, watch, onUnmounted } from 'vue'
|
|
159
|
+
|
|
160
|
+
const unwatchers = []
|
|
161
|
+
|
|
162
|
+
function createDynamicWatcher(source, callback) {
|
|
163
|
+
const unwatch = watch(source, callback)
|
|
164
|
+
unwatchers.push(unwatch)
|
|
165
|
+
return unwatch
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Clean up all dynamic watchers
|
|
169
|
+
onUnmounted(() => {
|
|
170
|
+
unwatchers.forEach(unwatch => unwatch())
|
|
171
|
+
})
|
|
172
|
+
</script>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Reference
|
|
176
|
+
- [Vue.js Watchers - Stopping a Watcher](https://vuejs.org/guide/essentials/watchers.html#stopping-a-watcher)
|