@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,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Pass v-for Data to Components via Props
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Components have isolated scope - v-for iteration data is not automatically available inside child components
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, v-for, components, props, list-rendering]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Always Pass v-for Data to Components via Props
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue components have isolated scope by design. When using `v-for` to render components, the iteration variable (e.g., `item`) is NOT automatically available inside the child component. You must explicitly pass the data through props.
|
|
12
|
+
|
|
13
|
+
This isolation is intentional - it keeps components reusable and their data dependencies explicit. Forgetting to pass props results in `undefined` data or errors about missing properties.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always bind iteration data to component props explicitly
|
|
18
|
+
- [ ] Pass both the item and index if the component needs them
|
|
19
|
+
- [ ] Always include a unique `:key` when rendering components with v-for
|
|
20
|
+
- [ ] Define corresponding props in the child component to receive the data
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```html
|
|
24
|
+
<!-- WRONG: Component cannot access 'todo' - it's not in scope -->
|
|
25
|
+
<TodoItem v-for="todo in todos" :key="todo.id" />
|
|
26
|
+
|
|
27
|
+
<!-- Inside TodoItem.vue: this.todo or todo is undefined! -->
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- WRONG: Key provided but no data passed -->
|
|
32
|
+
<UserCard
|
|
33
|
+
v-for="user in users"
|
|
34
|
+
:key="user.id"
|
|
35
|
+
/>
|
|
36
|
+
<!-- UserCard has no user data to display -->
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Correct:**
|
|
40
|
+
```html
|
|
41
|
+
<!-- CORRECT: Explicitly pass the item as a prop -->
|
|
42
|
+
<TodoItem
|
|
43
|
+
v-for="todo in todos"
|
|
44
|
+
:key="todo.id"
|
|
45
|
+
:todo="todo"
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```html
|
|
50
|
+
<!-- CORRECT: Pass multiple pieces of data -->
|
|
51
|
+
<UserCard
|
|
52
|
+
v-for="(user, index) in users"
|
|
53
|
+
:key="user.id"
|
|
54
|
+
:user="user"
|
|
55
|
+
:index="index"
|
|
56
|
+
:is-first="index === 0"
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<!-- Child component: UserCard.vue -->
|
|
62
|
+
<script setup>
|
|
63
|
+
defineProps({
|
|
64
|
+
user: {
|
|
65
|
+
type: Object,
|
|
66
|
+
required: true
|
|
67
|
+
},
|
|
68
|
+
index: {
|
|
69
|
+
type: Number,
|
|
70
|
+
required: true
|
|
71
|
+
},
|
|
72
|
+
isFirst: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<div class="user-card">
|
|
81
|
+
<span>{{ index + 1 }}. {{ user.name }}</span>
|
|
82
|
+
<span v-if="isFirst">(First User)</span>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Why Explicit Props?
|
|
88
|
+
|
|
89
|
+
1. **Clear data flow**: Makes dependencies explicit and traceable
|
|
90
|
+
2. **Reusability**: Components work anywhere, not just inside specific v-for loops
|
|
91
|
+
3. **Type safety**: Props can be validated with type and required checks
|
|
92
|
+
4. **Maintainability**: Easier to understand what data a component needs
|
|
93
|
+
|
|
94
|
+
## Reference
|
|
95
|
+
- [Vue.js List Rendering - v-for with Components](https://vuejs.org/guide/essentials/list.html#v-for-with-a-component)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Copy Arrays Before reverse() or sort() in Computed Properties
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: reverse() and sort() mutate the original array, causing unintended side effects in computed getters
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, v-for, computed, array, mutation, list-rendering]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Copy Arrays Before reverse() or sort() in Computed Properties
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - The `reverse()` and `sort()` methods mutate the original array in-place. When used directly in a computed property getter, this causes the source array to be modified, leading to infinite reactivity loops or incorrect data state.
|
|
12
|
+
|
|
13
|
+
Computed properties should be pure - they calculate a value without side effects. Mutating the source data inside a computed getter violates this principle and causes unpredictable behavior.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always create a copy of the array before calling `reverse()` or `sort()` in computed properties
|
|
18
|
+
- [ ] Use spread operator `[...array]` or `Array.from(array)` or `array.slice()` to copy
|
|
19
|
+
- [ ] This applies to any in-place mutation method used in computed getters
|
|
20
|
+
- [ ] Consider using `toSorted()` and `toReversed()` (ES2023) which return new arrays
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
const numbers = ref([1, 2, 3, 4, 5])
|
|
25
|
+
|
|
26
|
+
// WRONG: Mutates the original array
|
|
27
|
+
const reversedNumbers = computed(() => {
|
|
28
|
+
return numbers.value.reverse() // Modifies numbers.value!
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// WRONG: Same issue with sort
|
|
32
|
+
const sortedItems = computed(() => {
|
|
33
|
+
return items.value.sort((a, b) => a.name.localeCompare(b.name))
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Correct:**
|
|
38
|
+
```javascript
|
|
39
|
+
const numbers = ref([1, 2, 3, 4, 5])
|
|
40
|
+
|
|
41
|
+
// CORRECT: Create a copy first with spread operator
|
|
42
|
+
const reversedNumbers = computed(() => {
|
|
43
|
+
return [...numbers.value].reverse()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// CORRECT: Create a copy before sorting
|
|
47
|
+
const sortedItems = computed(() => {
|
|
48
|
+
return [...items.value].sort((a, b) => a.name.localeCompare(b.name))
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// CORRECT: Using slice() to copy
|
|
52
|
+
const reversedNumbers = computed(() => {
|
|
53
|
+
return numbers.value.slice().reverse()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// CORRECT: ES2023 non-mutating methods (if supported)
|
|
57
|
+
const reversedNumbers = computed(() => {
|
|
58
|
+
return numbers.value.toReversed()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const sortedItems = computed(() => {
|
|
62
|
+
return items.value.toSorted((a, b) => a.price - b.price)
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Also Applies to Methods Used in Templates
|
|
67
|
+
|
|
68
|
+
When using methods to filter/sort in nested v-for loops, the same rule applies:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// CORRECT: Method that doesn't mutate
|
|
72
|
+
function getSortedChildren(parent) {
|
|
73
|
+
return [...parent.children].sort((a, b) => a.order - b.order)
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<ul v-for="parent in parents" :key="parent.id">
|
|
79
|
+
<li v-for="child in getSortedChildren(parent)" :key="child.id">
|
|
80
|
+
{{ child.name }}
|
|
81
|
+
</li>
|
|
82
|
+
</ul>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Reference
|
|
86
|
+
- [Vue.js List Rendering - Displaying Filtered/Sorted Results](https://vuejs.org/guide/essentials/list.html#displaying-filtered-sorted-results)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Provide Unique Keys in v-for Loops
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Missing or improper keys cause hard-to-debug bugs when list items have state, components, or animations
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, v-for, list-rendering, key, state, components]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Always Provide Unique Keys in v-for Loops
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Without proper keys, Vue cannot track element identity when lists change. This causes component state loss, incorrect animations, form input values jumping between items, and bugs that are extremely difficult to debug.
|
|
12
|
+
|
|
13
|
+
The `key` attribute tells Vue how your data relates to the DOM elements it renders. When data ordering changes (via sort, filter, add, or remove), Vue uses keys to determine what to update, remove, or create. Without unique keys, Vue reuses DOM elements in-place which can cause one item's state to incorrectly appear on another item.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always provide `:key` with unique, stable identifiers (database IDs, UUIDs)
|
|
18
|
+
- [ ] Never use array index as key - indices shift when items are added/removed
|
|
19
|
+
- [ ] Use primitive values only (strings or numbers) - never use objects as keys
|
|
20
|
+
- [ ] On `<template v-for>`, place the key on the `<template>` tag itself (Vue 3 change)
|
|
21
|
+
- [ ] When using components in v-for, keys are mandatory, not optional
|
|
22
|
+
|
|
23
|
+
**Incorrect:**
|
|
24
|
+
```html
|
|
25
|
+
<!-- WRONG: No key provided -->
|
|
26
|
+
<li v-for="item in items">{{ item.name }}</li>
|
|
27
|
+
|
|
28
|
+
<!-- WRONG: Using array index as key - shifts when list changes -->
|
|
29
|
+
<li v-for="(item, index) in items" :key="index">
|
|
30
|
+
<input v-model="item.value" />
|
|
31
|
+
</li>
|
|
32
|
+
|
|
33
|
+
<!-- WRONG: Object as key -->
|
|
34
|
+
<li v-for="item in items" :key="item">{{ item.name }}</li>
|
|
35
|
+
|
|
36
|
+
<!-- WRONG (Vue 3): Key on child instead of template -->
|
|
37
|
+
<template v-for="item in items">
|
|
38
|
+
<li :key="item.id">{{ item.name }}</li>
|
|
39
|
+
</template>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Bug demonstration: Using index as key
|
|
44
|
+
// Initial: ['Alice', 'Bob', 'Charlie'] at indices [0, 1, 2]
|
|
45
|
+
// After removing 'Bob': ['Alice', 'Charlie'] at indices [0, 1]
|
|
46
|
+
// Charlie now has index 1, so Vue reuses Bob's DOM/component state for Charlie!
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Correct:**
|
|
50
|
+
```html
|
|
51
|
+
<!-- CORRECT: Unique identifier as key -->
|
|
52
|
+
<li v-for="item in items" :key="item.id">
|
|
53
|
+
{{ item.name }}
|
|
54
|
+
</li>
|
|
55
|
+
|
|
56
|
+
<!-- CORRECT: With components -->
|
|
57
|
+
<MyComponent
|
|
58
|
+
v-for="item in items"
|
|
59
|
+
:key="item.id"
|
|
60
|
+
:item="item"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<!-- CORRECT (Vue 3): Key on template tag -->
|
|
64
|
+
<template v-for="item in items" :key="item.id">
|
|
65
|
+
<li>{{ item.name }}</li>
|
|
66
|
+
<span>{{ item.description }}</span>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<!-- CORRECT: With stateful elements -->
|
|
70
|
+
<div v-for="user in users" :key="user.id">
|
|
71
|
+
<input v-model="user.email" />
|
|
72
|
+
<select v-model="user.role">
|
|
73
|
+
<option value="admin">Admin</option>
|
|
74
|
+
<option value="user">User</option>
|
|
75
|
+
</select>
|
|
76
|
+
</div>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## When Keys Are Critical
|
|
80
|
+
|
|
81
|
+
Keys are absolutely required when v-for loops contain:
|
|
82
|
+
- Components with local state
|
|
83
|
+
- Form elements (`<input>`, `<select>`, `<textarea>`)
|
|
84
|
+
- Elements with initialization logic (mounted/created hooks)
|
|
85
|
+
- Animations or transitions
|
|
86
|
+
- Direct DOM manipulation
|
|
87
|
+
|
|
88
|
+
## Reference
|
|
89
|
+
- [Vue.js List Rendering - Key](https://vuejs.org/guide/essentials/list.html#maintaining-state-with-key)
|
|
90
|
+
- [Vue 3 Migration Guide - Key on Template](https://v3-migration.vuejs.org/breaking-changes/key-attribute)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: v-for Range Iteration Starts at 1, Not 0
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: v-for with a number range starts at 1, unlike JavaScript arrays which start at 0
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, v-for, list-rendering, range]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# v-for Range Iteration Starts at 1, Not 0
|
|
10
|
+
|
|
11
|
+
**Impact: LOW** - When using `v-for` with a number (range iteration), the iteration starts at `1`, not `0`. This differs from typical JavaScript behavior where arrays are 0-indexed.
|
|
12
|
+
|
|
13
|
+
This gotcha commonly causes off-by-one errors when the generated numbers are used for calculations or array indexing.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Remember `v-for="n in 10"` produces 1 through 10, not 0 through 9
|
|
18
|
+
- [ ] When using range values for array indexing, subtract 1: `items[n - 1]`
|
|
19
|
+
- [ ] Consider creating a computed array if you need 0-based indices
|
|
20
|
+
|
|
21
|
+
**Incorrect Assumption:**
|
|
22
|
+
```html
|
|
23
|
+
<!-- Developer expects 0-9, but gets 1-10 -->
|
|
24
|
+
<span v-for="n in 10">{{ n }}</span>
|
|
25
|
+
<!-- Output: 1 2 3 4 5 6 7 8 9 10 -->
|
|
26
|
+
|
|
27
|
+
<!-- WRONG: Off-by-one error when used for array access -->
|
|
28
|
+
<li v-for="n in items.length" :key="n">
|
|
29
|
+
{{ items[n].name }} <!-- Error! items[10] is undefined when length is 10 -->
|
|
30
|
+
</li>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct:**
|
|
34
|
+
```html
|
|
35
|
+
<!-- Correct understanding: 1-based range -->
|
|
36
|
+
<span v-for="n in 10" :key="n">{{ n }}</span>
|
|
37
|
+
<!-- Output: 1 2 3 4 5 6 7 8 9 10 -->
|
|
38
|
+
|
|
39
|
+
<!-- CORRECT: Adjust index for array access -->
|
|
40
|
+
<li v-for="n in items.length" :key="n">
|
|
41
|
+
{{ items[n - 1].name }} <!-- n-1 converts to 0-based -->
|
|
42
|
+
</li>
|
|
43
|
+
|
|
44
|
+
<!-- BETTER: Just iterate the array directly -->
|
|
45
|
+
<li v-for="(item, index) in items" :key="item.id">
|
|
46
|
+
{{ index + 1 }}. {{ item.name }} <!-- index is 0-based, add 1 for display -->
|
|
47
|
+
</li>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<!-- Range for repeating elements (no array involved) -->
|
|
52
|
+
<div v-for="n in 3" :key="n" class="skeleton-row">
|
|
53
|
+
Loading placeholder {{ n }} of 3...
|
|
54
|
+
</div>
|
|
55
|
+
<!-- Output: Loading placeholder 1 of 3, 2 of 3, 3 of 3 -->
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## When Range Iteration Is Useful
|
|
59
|
+
|
|
60
|
+
- Rendering a fixed number of placeholder/skeleton elements
|
|
61
|
+
- Creating pagination buttons: `v-for="page in totalPages"`
|
|
62
|
+
- Generating star ratings: `v-for="star in 5"`
|
|
63
|
+
- Repeating template structures a set number of times
|
|
64
|
+
|
|
65
|
+
## Reference
|
|
66
|
+
- [Vue.js List Rendering - v-for with a Range](https://vuejs.org/guide/essentials/list.html#v-for-with-a-range)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Check for Null/Undefined Before Accessing Properties in v-if
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Accessing properties on null/undefined causes runtime errors and crashes
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, conditional-rendering, v-if, null-check, defensive-programming]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Check for Null/Undefined Before Accessing Properties in v-if
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Accessing properties on null or undefined objects in `v-if` conditions causes "Cannot read property of undefined" runtime errors. This commonly occurs when data is loaded asynchronously or when optional object properties are accessed without null checks.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Always check that an object exists before accessing its properties
|
|
16
|
+
- [ ] Use optional chaining (?.) in Vue 3 templates for cleaner null checks
|
|
17
|
+
- [ ] Consider using computed properties for complex conditional logic
|
|
18
|
+
- [ ] Handle loading states explicitly rather than relying on undefined checks
|
|
19
|
+
|
|
20
|
+
**Incorrect:**
|
|
21
|
+
```html
|
|
22
|
+
<!-- WRONG: Accessing property before checking object exists -->
|
|
23
|
+
<template>
|
|
24
|
+
<div v-if="user.isAdmin">
|
|
25
|
+
Admin Panel
|
|
26
|
+
</div>
|
|
27
|
+
<!-- Error if user is null/undefined: Cannot read property 'isAdmin' of undefined -->
|
|
28
|
+
</template>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- WRONG: Nested property access without checks -->
|
|
33
|
+
<template>
|
|
34
|
+
<div v-if="order.customer.address.city === 'NYC'">
|
|
35
|
+
Local delivery available
|
|
36
|
+
</div>
|
|
37
|
+
<!-- Error if any level is undefined -->
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<!-- WRONG: Array access without existence check -->
|
|
43
|
+
<template>
|
|
44
|
+
<div v-if="items[0].name === 'Featured'">
|
|
45
|
+
{{ items[0].description }}
|
|
46
|
+
</div>
|
|
47
|
+
<!-- Error if items is empty array -->
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Correct:**
|
|
52
|
+
```html
|
|
53
|
+
<!-- CORRECT: Check object exists first with && -->
|
|
54
|
+
<template>
|
|
55
|
+
<div v-if="user && user.isAdmin">
|
|
56
|
+
Admin Panel
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<!-- CORRECT: Optional chaining (Vue 3) -->
|
|
63
|
+
<template>
|
|
64
|
+
<div v-if="user?.isAdmin">
|
|
65
|
+
Admin Panel
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<!-- CORRECT: Optional chaining for nested properties -->
|
|
72
|
+
<template>
|
|
73
|
+
<div v-if="order?.customer?.address?.city === 'NYC'">
|
|
74
|
+
Local delivery available
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<!-- CORRECT: Array length check before access -->
|
|
81
|
+
<template>
|
|
82
|
+
<div v-if="items.length > 0 && items[0].name === 'Featured'">
|
|
83
|
+
{{ items[0].description }}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Or with optional chaining -->
|
|
87
|
+
<div v-if="items?.[0]?.name === 'Featured'">
|
|
88
|
+
{{ items[0].description }}
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<!-- CORRECT: Explicit loading state handling -->
|
|
95
|
+
<template>
|
|
96
|
+
<div v-if="isLoading">Loading...</div>
|
|
97
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
98
|
+
<div v-else-if="user">
|
|
99
|
+
<h1>Welcome, {{ user.name }}</h1>
|
|
100
|
+
<div v-if="user.isAdmin">Admin Panel</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div v-else>No user data</div>
|
|
103
|
+
</template>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Using Computed Properties for Complex Checks
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
// CORRECT: Move complex checks to computed properties
|
|
110
|
+
<script setup>
|
|
111
|
+
import { computed } from 'vue'
|
|
112
|
+
|
|
113
|
+
const props = defineProps(['user', 'permissions'])
|
|
114
|
+
|
|
115
|
+
const canAccessAdmin = computed(() => {
|
|
116
|
+
return props.user?.isAdmin &&
|
|
117
|
+
props.permissions?.includes('admin_panel') &&
|
|
118
|
+
!props.user?.isDisabled
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const userDisplayName = computed(() => {
|
|
122
|
+
return props.user?.profile?.displayName ||
|
|
123
|
+
props.user?.name ||
|
|
124
|
+
'Anonymous'
|
|
125
|
+
})
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<div v-if="canAccessAdmin">
|
|
130
|
+
Admin Panel
|
|
131
|
+
</div>
|
|
132
|
+
<span>{{ userDisplayName }}</span>
|
|
133
|
+
</template>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Common Async Data Pattern
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// CORRECT: Handle async data loading properly
|
|
140
|
+
<script setup>
|
|
141
|
+
import { ref, onMounted } from 'vue'
|
|
142
|
+
|
|
143
|
+
const user = ref(null)
|
|
144
|
+
const isLoading = ref(true)
|
|
145
|
+
const error = ref(null)
|
|
146
|
+
|
|
147
|
+
onMounted(async () => {
|
|
148
|
+
try {
|
|
149
|
+
user.value = await fetchUser()
|
|
150
|
+
} catch (e) {
|
|
151
|
+
error.value = e
|
|
152
|
+
} finally {
|
|
153
|
+
isLoading.value = false
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<template>
|
|
159
|
+
<div v-if="isLoading">Loading user...</div>
|
|
160
|
+
<div v-else-if="error">Failed to load user</div>
|
|
161
|
+
<div v-else-if="user">
|
|
162
|
+
<!-- Safe to access user properties here -->
|
|
163
|
+
<h1>{{ user.name }}</h1>
|
|
164
|
+
<p v-if="user.bio">{{ user.bio }}</p>
|
|
165
|
+
</div>
|
|
166
|
+
</template>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Reference
|
|
170
|
+
- [Vue.js Conditional Rendering](https://vuejs.org/guide/essentials/conditional.html)
|
|
171
|
+
- [MDN - Optional Chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: v-model Ignores Initial HTML Attributes
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: v-model ignores value, checked, and selected HTML attributes - initial state must be set in JavaScript
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, v-model, forms, input, checkbox, select, initialization]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# v-model Ignores Initial HTML Attributes
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Setting initial values via HTML `value`, `checked`, or `selected` attributes has no effect when using v-model. The form will appear empty or in an unexpected state, confusing users and potentially causing data loss.
|
|
12
|
+
|
|
13
|
+
Vue's v-model always treats the bound JavaScript state as the single source of truth. Any initial attributes in the HTML are completely ignored. This is a common mistake when migrating from plain HTML forms or when copying HTML templates.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never rely on HTML `value`, `checked`, or `selected` attributes when using v-model
|
|
18
|
+
- [ ] Always declare initial values in JavaScript using `ref()` or `reactive()`
|
|
19
|
+
- [ ] When migrating plain HTML forms to Vue, move all default values to JavaScript state
|
|
20
|
+
- [ ] Audit existing forms for hardcoded HTML default values that may be silently ignored
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```html
|
|
24
|
+
<script setup>
|
|
25
|
+
import { ref } from 'vue'
|
|
26
|
+
|
|
27
|
+
const username = ref('') // Empty!
|
|
28
|
+
const isSubscribed = ref(false) // Not checked!
|
|
29
|
+
const country = ref('') // No default selection!
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<!-- WRONG: These HTML attributes are completely ignored -->
|
|
34
|
+
<input v-model="username" value="default_user">
|
|
35
|
+
|
|
36
|
+
<input type="checkbox" v-model="isSubscribed" checked>
|
|
37
|
+
|
|
38
|
+
<select v-model="country">
|
|
39
|
+
<option value="us" selected>United States</option>
|
|
40
|
+
<option value="uk">United Kingdom</option>
|
|
41
|
+
</select>
|
|
42
|
+
</template>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Correct:**
|
|
46
|
+
```html
|
|
47
|
+
<script setup>
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
|
|
50
|
+
// CORRECT: Set initial values in JavaScript
|
|
51
|
+
const username = ref('default_user')
|
|
52
|
+
const isSubscribed = ref(true)
|
|
53
|
+
const country = ref('us')
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<!-- HTML attributes not needed - JavaScript state controls everything -->
|
|
58
|
+
<input v-model="username">
|
|
59
|
+
|
|
60
|
+
<input type="checkbox" v-model="isSubscribed">
|
|
61
|
+
|
|
62
|
+
<select v-model="country">
|
|
63
|
+
<option value="us">United States</option>
|
|
64
|
+
<option value="uk">United Kingdom</option>
|
|
65
|
+
</select>
|
|
66
|
+
</template>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// Options API equivalent
|
|
71
|
+
export default {
|
|
72
|
+
data() {
|
|
73
|
+
return {
|
|
74
|
+
username: 'default_user',
|
|
75
|
+
isSubscribed: true,
|
|
76
|
+
country: 'us'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Reference
|
|
83
|
+
- [Vue.js Form Input Bindings](https://vuejs.org/guide/essentials/forms.html)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: v-model Does Not Update During IME Composition
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: v-model won't capture intermediate input for Chinese, Japanese, Korean and other IME languages
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, v-model, forms, input, ime, internationalization, i18n, cjk]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# v-model Does Not Update During IME Composition
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When users type in languages that require an Input Method Editor (Chinese, Japanese, Korean, etc.), v-model won't update until the composition is complete. This breaks real-time validation, character counters, and live search features for international users.
|
|
12
|
+
|
|
13
|
+
IME (Input Method Editor) allows users to compose complex characters by typing multiple keystrokes. During this composition phase, v-model deliberately waits until the user confirms their selection before updating. This is usually desired, but can break features that need every keystroke.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Test forms with IME input if your app serves CJK (Chinese, Japanese, Korean) users
|
|
18
|
+
- [ ] For real-time features (live search, character counters), use manual event binding instead of v-model
|
|
19
|
+
- [ ] Consider both behaviors when designing - sometimes waiting for composition completion IS correct
|
|
20
|
+
- [ ] Document expected behavior for international users
|
|
21
|
+
|
|
22
|
+
**Problem - v-model waits for composition:**
|
|
23
|
+
```html
|
|
24
|
+
<script setup>
|
|
25
|
+
import { ref } from 'vue'
|
|
26
|
+
|
|
27
|
+
const searchQuery = ref('')
|
|
28
|
+
const charCount = ref(0)
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<!-- PROBLEM: During IME composition, searchQuery won't update -->
|
|
33
|
+
<!-- Chinese user types "ni hao" -> sees no results until they press space/enter -->
|
|
34
|
+
<input v-model="searchQuery" placeholder="Live search...">
|
|
35
|
+
<p>{{ searchQuery.length }} characters</p>
|
|
36
|
+
|
|
37
|
+
<!-- Character counter stays at 0 during IME input -->
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Solution - Manual event binding for real-time updates:**
|
|
42
|
+
```html
|
|
43
|
+
<script setup>
|
|
44
|
+
import { ref } from 'vue'
|
|
45
|
+
|
|
46
|
+
const searchQuery = ref('')
|
|
47
|
+
|
|
48
|
+
// This captures EVERY input event, including during IME composition
|
|
49
|
+
function handleInput(event) {
|
|
50
|
+
searchQuery.value = event.target.value
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<!-- CORRECT: Manual binding captures all input, including IME composition -->
|
|
56
|
+
<input
|
|
57
|
+
:value="searchQuery"
|
|
58
|
+
@input="handleInput"
|
|
59
|
+
placeholder="Live search..."
|
|
60
|
+
>
|
|
61
|
+
<p>{{ searchQuery.length }} characters</p>
|
|
62
|
+
</template>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<!-- Shorter inline version -->
|
|
67
|
+
<input
|
|
68
|
+
:value="searchQuery"
|
|
69
|
+
@input="event => searchQuery = event.target.value"
|
|
70
|
+
>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**When v-model behavior IS correct:**
|
|
74
|
+
```html
|
|
75
|
+
<!-- For form submission, waiting for composition IS usually better -->
|
|
76
|
+
<!-- User expects to confirm their character selection before it's "official" -->
|
|
77
|
+
<input v-model="formName" placeholder="Enter your name">
|
|
78
|
+
|
|
79
|
+
<!-- The final composed characters will be submitted, not intermediate states -->
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Reference
|
|
83
|
+
- [Vue.js Form Input Bindings](https://vuejs.org/guide/essentials/forms.html)
|