@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,115 @@
|
|
|
1
|
+
# Async Component Error Handling
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
Always configure error handling for async components using `errorComponent` and/or `onError` callback. Without proper error handling, failed component loads can leave the UI in an undefined state with no user feedback.
|
|
6
|
+
|
|
7
|
+
## Why This Matters
|
|
8
|
+
|
|
9
|
+
Network failures, timeouts, and server errors are common in production. Without error handling, users see blank spaces or broken UIs with no indication of what went wrong or how to recover.
|
|
10
|
+
|
|
11
|
+
## Bad Code
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<script setup>
|
|
15
|
+
import { defineAsyncComponent } from 'vue'
|
|
16
|
+
|
|
17
|
+
// No error handling - fails silently
|
|
18
|
+
const AsyncWidget = defineAsyncComponent(() =>
|
|
19
|
+
import('./Widget.vue')
|
|
20
|
+
)
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```vue
|
|
25
|
+
<script setup>
|
|
26
|
+
import { defineAsyncComponent } from 'vue'
|
|
27
|
+
|
|
28
|
+
// isLoading never becomes false on error - infinite spinner
|
|
29
|
+
const isLoading = ref(true)
|
|
30
|
+
const Widget = defineAsyncComponent({
|
|
31
|
+
loader: () => import('./Widget.vue').finally(() => {
|
|
32
|
+
isLoading.value = false // Only runs on success
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Good Code
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<script setup>
|
|
42
|
+
import { defineAsyncComponent } from 'vue'
|
|
43
|
+
import LoadingSpinner from './LoadingSpinner.vue'
|
|
44
|
+
import ErrorDisplay from './ErrorDisplay.vue'
|
|
45
|
+
|
|
46
|
+
const AsyncWidget = defineAsyncComponent({
|
|
47
|
+
loader: () => import('./Widget.vue'),
|
|
48
|
+
loadingComponent: LoadingSpinner,
|
|
49
|
+
errorComponent: ErrorDisplay,
|
|
50
|
+
delay: 200, // Prevent loading flicker
|
|
51
|
+
timeout: 10000 // Show error after 10 seconds
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<script setup>
|
|
58
|
+
import { defineAsyncComponent } from 'vue'
|
|
59
|
+
|
|
60
|
+
// With retry logic using onError
|
|
61
|
+
const AsyncWidget = defineAsyncComponent({
|
|
62
|
+
loader: () => import('./Widget.vue'),
|
|
63
|
+
loadingComponent: LoadingSpinner,
|
|
64
|
+
errorComponent: ErrorDisplay,
|
|
65
|
+
onError(error, retry, fail, attempts) {
|
|
66
|
+
if (attempts <= 3) {
|
|
67
|
+
// Retry up to 3 times
|
|
68
|
+
retry()
|
|
69
|
+
} else {
|
|
70
|
+
// Give up and show error component
|
|
71
|
+
fail()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<script setup>
|
|
80
|
+
import { defineAsyncComponent } from 'vue'
|
|
81
|
+
|
|
82
|
+
// Fallback component pattern - catch in loader
|
|
83
|
+
const AsyncWidget = defineAsyncComponent(() =>
|
|
84
|
+
import('./Widget.vue').catch(() => import('./WidgetFallback.vue'))
|
|
85
|
+
)
|
|
86
|
+
</script>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## onError Callback Parameters
|
|
90
|
+
|
|
91
|
+
The `onError` callback receives four arguments:
|
|
92
|
+
|
|
93
|
+
| Parameter | Type | Description |
|
|
94
|
+
|-----------|------|-------------|
|
|
95
|
+
| `error` | `Error` | The error that caused the load to fail |
|
|
96
|
+
| `retry` | `Function` | Call to retry loading the component |
|
|
97
|
+
| `fail` | `Function` | Call to give up and show errorComponent |
|
|
98
|
+
| `attempts` | `number` | Number of load attempts so far |
|
|
99
|
+
|
|
100
|
+
## Key Points
|
|
101
|
+
|
|
102
|
+
1. Always provide an `errorComponent` for production applications
|
|
103
|
+
2. Use `timeout` to prevent indefinite loading states
|
|
104
|
+
3. Consider retry logic with `onError` for transient network issues
|
|
105
|
+
4. The `delay` option (default 200ms) prevents loading flicker on fast networks
|
|
106
|
+
5. Use the fallback pattern (`.catch()` in loader) when you want a seamless degradation
|
|
107
|
+
|
|
108
|
+
## SSR Warning
|
|
109
|
+
|
|
110
|
+
Using `onError` with SSR can cause issues in some configurations, potentially leading to infinite loading. Test thoroughly in SSR environments.
|
|
111
|
+
|
|
112
|
+
## References
|
|
113
|
+
|
|
114
|
+
- [Vue.js Async Components Documentation](https://vuejs.org/guide/components/async)
|
|
115
|
+
- [Handling Async Components' loading errors](https://awad.dev/blog/handling-async-component-loading-errors/)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Async Components with keep-alive Ref Issues
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
When using `<keep-alive>`, `<component>`, and `defineAsyncComponent` together, be aware that template refs can become undefined when the component is re-activated after being deactivated.
|
|
6
|
+
|
|
7
|
+
## Why This Matters
|
|
8
|
+
|
|
9
|
+
This is a known Vue issue where the ref binding works correctly on first activation but becomes undefined on subsequent activations. This can cause runtime errors when trying to access component methods or properties through refs.
|
|
10
|
+
|
|
11
|
+
## Problem Scenario
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<script setup>
|
|
15
|
+
import { ref, defineAsyncComponent } from 'vue'
|
|
16
|
+
|
|
17
|
+
const AsyncWidget = defineAsyncComponent(() =>
|
|
18
|
+
import('./Widget.vue')
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const currentComponent = ref(AsyncWidget)
|
|
22
|
+
const widgetRef = ref(null)
|
|
23
|
+
|
|
24
|
+
function callWidgetMethod() {
|
|
25
|
+
// May be undefined after component reactivation!
|
|
26
|
+
widgetRef.value?.doSomething()
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<keep-alive>
|
|
32
|
+
<component :is="currentComponent" ref="widgetRef" />
|
|
33
|
+
</keep-alive>
|
|
34
|
+
</template>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Workarounds
|
|
38
|
+
|
|
39
|
+
### Option 1: Use onActivated to re-establish ref access
|
|
40
|
+
|
|
41
|
+
```vue
|
|
42
|
+
<script setup>
|
|
43
|
+
import { ref, defineAsyncComponent, onActivated, nextTick } from 'vue'
|
|
44
|
+
|
|
45
|
+
const AsyncWidget = defineAsyncComponent(() =>
|
|
46
|
+
import('./Widget.vue')
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const currentComponent = ref(AsyncWidget)
|
|
50
|
+
const widgetRef = ref(null)
|
|
51
|
+
|
|
52
|
+
// Use a computed or method that waits for ref to be available
|
|
53
|
+
async function callWidgetMethod() {
|
|
54
|
+
await nextTick()
|
|
55
|
+
if (widgetRef.value) {
|
|
56
|
+
widgetRef.value.doSomething()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Option 2: Avoid mixing all three patterns
|
|
63
|
+
|
|
64
|
+
If possible, use one of these alternatives:
|
|
65
|
+
|
|
66
|
+
```vue
|
|
67
|
+
<!-- Option A: Don't use keep-alive with async components -->
|
|
68
|
+
<template>
|
|
69
|
+
<component :is="currentComponent" ref="widgetRef" />
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<!-- Option B: Use static component with keep-alive -->
|
|
73
|
+
<script setup>
|
|
74
|
+
import Widget from './Widget.vue' // Regular import
|
|
75
|
+
</script>
|
|
76
|
+
<template>
|
|
77
|
+
<keep-alive>
|
|
78
|
+
<component :is="Widget" ref="widgetRef" />
|
|
79
|
+
</keep-alive>
|
|
80
|
+
</template>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Option 3: Use provide/inject instead of refs
|
|
84
|
+
|
|
85
|
+
```vue
|
|
86
|
+
<!-- Parent.vue -->
|
|
87
|
+
<script setup>
|
|
88
|
+
import { provide, ref } from 'vue'
|
|
89
|
+
|
|
90
|
+
const sharedState = ref({ /* shared data */ })
|
|
91
|
+
provide('widgetState', sharedState)
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<!-- Widget.vue (async component) -->
|
|
95
|
+
<script setup>
|
|
96
|
+
import { inject } from 'vue'
|
|
97
|
+
const widgetState = inject('widgetState')
|
|
98
|
+
</script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Key Points
|
|
102
|
+
|
|
103
|
+
1. This is a known issue when combining `<keep-alive>`, `<component :is>`, and `defineAsyncComponent`
|
|
104
|
+
2. Refs may become undefined after component deactivation/reactivation
|
|
105
|
+
3. Use `nextTick` and null checks when accessing refs
|
|
106
|
+
4. Consider alternative patterns like provide/inject for cross-component communication
|
|
107
|
+
5. Test thoroughly when using this combination
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
- [Vue.js GitHub Discussion #11334](https://github.com/orgs/vuejs/discussions/11334)
|
|
112
|
+
- [Vue.js Async Components Documentation](https://vuejs.org/guide/components/async)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Suspense Overrides Async Component Loading and Error Options
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Async component loading/error options are ignored under a parent Suspense, leading to missing spinners and error UIs
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, suspense, async-components, loading, error-handling]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Suspense Overrides Async Component Loading and Error Options
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When an async component renders inside a parent `<Suspense>`, its `loadingComponent`, `errorComponent`, `delay`, and `timeout` options do not run. The parent Suspense controls loading and error UX instead.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Confirm whether the async component is inside a `<Suspense>` boundary
|
|
16
|
+
- [ ] Use `suspensible: false` when the component must manage its own loading/error UI
|
|
17
|
+
- [ ] Or move loading/error UI to the parent `<Suspense>` fallback and an error boundary (`onErrorCaptured`)
|
|
18
|
+
- [ ] Provide a retry path for failed loads
|
|
19
|
+
|
|
20
|
+
**Incorrect:**
|
|
21
|
+
```vue
|
|
22
|
+
<script setup>
|
|
23
|
+
import { defineAsyncComponent } from 'vue'
|
|
24
|
+
|
|
25
|
+
const AsyncDashboard = defineAsyncComponent({
|
|
26
|
+
loader: () => import('./Dashboard.vue'),
|
|
27
|
+
loadingComponent: LoadingSpinner,
|
|
28
|
+
errorComponent: ErrorDisplay,
|
|
29
|
+
timeout: 3000
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<Suspense>
|
|
35
|
+
<AsyncDashboard />
|
|
36
|
+
<template #fallback>Loading...</template>
|
|
37
|
+
</Suspense>
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Correct (component handles its own states):**
|
|
42
|
+
```vue
|
|
43
|
+
<script setup>
|
|
44
|
+
import { defineAsyncComponent } from 'vue'
|
|
45
|
+
|
|
46
|
+
const AsyncDashboard = defineAsyncComponent({
|
|
47
|
+
loader: () => import('./Dashboard.vue'),
|
|
48
|
+
loadingComponent: LoadingSpinner,
|
|
49
|
+
errorComponent: ErrorDisplay,
|
|
50
|
+
timeout: 3000,
|
|
51
|
+
suspensible: false
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<AsyncDashboard />
|
|
57
|
+
</template>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Correct (parent Suspense owns loading/error UI):**
|
|
61
|
+
```vue
|
|
62
|
+
<script setup>
|
|
63
|
+
import { onErrorCaptured, ref } from 'vue'
|
|
64
|
+
import AsyncDashboard from './AsyncDashboard.vue'
|
|
65
|
+
|
|
66
|
+
const error = ref(null)
|
|
67
|
+
|
|
68
|
+
onErrorCaptured((err) => {
|
|
69
|
+
error.value = err
|
|
70
|
+
return false
|
|
71
|
+
})
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<ErrorDisplay v-if="error" :error="error" />
|
|
76
|
+
|
|
77
|
+
<Suspense v-else>
|
|
78
|
+
<AsyncDashboard />
|
|
79
|
+
<template #fallback>
|
|
80
|
+
<LoadingSpinner />
|
|
81
|
+
</template>
|
|
82
|
+
</Suspense>
|
|
83
|
+
</template>
|
|
84
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Do Not Use defineAsyncComponent with Vue Router
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
Never use `defineAsyncComponent` when configuring Vue Router route components. Vue Router has its own lazy loading mechanism using dynamic imports directly.
|
|
6
|
+
|
|
7
|
+
## Why This Matters
|
|
8
|
+
|
|
9
|
+
Vue Router's lazy loading is specifically designed for route-level code splitting. Using `defineAsyncComponent` for routes adds unnecessary overhead and can cause unexpected behavior with navigation guards, loading states, and route transitions.
|
|
10
|
+
|
|
11
|
+
## Bad Code
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { defineAsyncComponent } from 'vue'
|
|
15
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
16
|
+
|
|
17
|
+
const router = createRouter({
|
|
18
|
+
history: createWebHistory(),
|
|
19
|
+
routes: [
|
|
20
|
+
{
|
|
21
|
+
path: '/dashboard',
|
|
22
|
+
// WRONG: Don't use defineAsyncComponent here
|
|
23
|
+
component: defineAsyncComponent(() =>
|
|
24
|
+
import('./views/Dashboard.vue')
|
|
25
|
+
)
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '/profile',
|
|
29
|
+
// WRONG: This also won't work as expected
|
|
30
|
+
component: defineAsyncComponent({
|
|
31
|
+
loader: () => import('./views/Profile.vue'),
|
|
32
|
+
loadingComponent: LoadingSpinner
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Good Code
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
43
|
+
|
|
44
|
+
const router = createRouter({
|
|
45
|
+
history: createWebHistory(),
|
|
46
|
+
routes: [
|
|
47
|
+
{
|
|
48
|
+
path: '/dashboard',
|
|
49
|
+
// CORRECT: Use dynamic import directly
|
|
50
|
+
component: () => import('./views/Dashboard.vue')
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path: '/profile',
|
|
54
|
+
// CORRECT: Simple arrow function with import
|
|
55
|
+
component: () => import('./views/Profile.vue')
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Handling Loading States with Vue Router
|
|
62
|
+
|
|
63
|
+
For route-level loading states, use Vue Router's navigation guards or a global loading indicator:
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<script setup>
|
|
67
|
+
import { ref } from 'vue'
|
|
68
|
+
import { useRouter } from 'vue-router'
|
|
69
|
+
|
|
70
|
+
const router = useRouter()
|
|
71
|
+
const isLoading = ref(false)
|
|
72
|
+
|
|
73
|
+
router.beforeEach(() => {
|
|
74
|
+
isLoading.value = true
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
router.afterEach(() => {
|
|
78
|
+
isLoading.value = false
|
|
79
|
+
})
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<LoadingBar v-if="isLoading" />
|
|
84
|
+
<RouterView />
|
|
85
|
+
</template>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## When to Use defineAsyncComponent
|
|
89
|
+
|
|
90
|
+
Use `defineAsyncComponent` for:
|
|
91
|
+
- Components loaded conditionally within a page
|
|
92
|
+
- Heavy components that aren't always needed
|
|
93
|
+
- Modal dialogs or panels that load on demand
|
|
94
|
+
|
|
95
|
+
Use Vue Router's lazy loading for:
|
|
96
|
+
- Route-level components (views/pages)
|
|
97
|
+
- Any component configured in route definitions
|
|
98
|
+
|
|
99
|
+
## Key Points
|
|
100
|
+
|
|
101
|
+
1. Vue Router and `defineAsyncComponent` are separate lazy loading mechanisms
|
|
102
|
+
2. Route components should use direct dynamic imports: `() => import('./View.vue')`
|
|
103
|
+
3. Use navigation guards for route-level loading indicators
|
|
104
|
+
4. `defineAsyncComponent` is for component-level lazy loading within pages
|
|
105
|
+
|
|
106
|
+
## References
|
|
107
|
+
|
|
108
|
+
- [Vue Router Lazy Loading Routes](https://router.vuejs.org/guide/advanced/lazy-loading.html)
|
|
109
|
+
- [Vue.js Async Components Documentation](https://vuejs.org/guide/components/async)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Fallthrough Event Listeners Are Additive
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
When an event listener is passed to a component as a fallthrough attribute, it is added to the root element's existing listeners of the same type - both will trigger. This is different from props where values are replaced. Be aware that both the component's internal handler and the parent's handler will execute.
|
|
6
|
+
|
|
7
|
+
## Why This Matters
|
|
8
|
+
|
|
9
|
+
- Developers may expect event listeners to override like props
|
|
10
|
+
- Both handlers execute, which can cause double submissions, duplicate API calls
|
|
11
|
+
- Order of execution: internal handler first, then fallthrough handler
|
|
12
|
+
- This is actually useful for composition but can cause bugs if unexpected
|
|
13
|
+
|
|
14
|
+
## Bad Code
|
|
15
|
+
|
|
16
|
+
```vue
|
|
17
|
+
<!-- BaseButton.vue - Potential double-action bug -->
|
|
18
|
+
<template>
|
|
19
|
+
<button @click="internalClick">
|
|
20
|
+
<slot />
|
|
21
|
+
</button>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup>
|
|
25
|
+
const emit = defineEmits(['action'])
|
|
26
|
+
|
|
27
|
+
function internalClick() {
|
|
28
|
+
// This runs first
|
|
29
|
+
emit('action')
|
|
30
|
+
console.log('Internal click handler')
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- Parent.vue -->
|
|
35
|
+
<template>
|
|
36
|
+
<BaseButton @click="parentClick">Submit</BaseButton>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
function parentClick() {
|
|
41
|
+
// This ALSO runs (after internal)
|
|
42
|
+
submitForm() // Might cause double submission!
|
|
43
|
+
console.log('Parent click handler')
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<!--
|
|
48
|
+
RESULT: Both handlers fire!
|
|
49
|
+
Console output:
|
|
50
|
+
1. "Internal click handler"
|
|
51
|
+
2. "Parent click handler"
|
|
52
|
+
|
|
53
|
+
If both trigger API calls, you get duplicate requests
|
|
54
|
+
-->
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Good Code
|
|
58
|
+
|
|
59
|
+
### Option 1: Prevent fallthrough with inheritAttrs: false
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<!-- BaseButton.vue - Control event handling explicitly -->
|
|
63
|
+
<script setup>
|
|
64
|
+
defineOptions({
|
|
65
|
+
inheritAttrs: false
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const emit = defineEmits(['click'])
|
|
69
|
+
|
|
70
|
+
function handleClick(event) {
|
|
71
|
+
// Component controls all click behavior
|
|
72
|
+
console.log('Handled internally')
|
|
73
|
+
emit('click', event) // Explicitly forward if needed
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<button @click="handleClick">
|
|
79
|
+
<slot />
|
|
80
|
+
</button>
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Option 2: Document the additive behavior
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<!-- BaseButton.vue - Design for composition -->
|
|
88
|
+
<script setup>
|
|
89
|
+
/**
|
|
90
|
+
* BaseButton - A composable button component
|
|
91
|
+
*
|
|
92
|
+
* Note: Click handlers passed to this component are ADDITIVE.
|
|
93
|
+
* The internal handler runs first, then any parent @click handler.
|
|
94
|
+
* Use @action event if you only want to respond to the action.
|
|
95
|
+
*/
|
|
96
|
+
const emit = defineEmits(['action'])
|
|
97
|
+
|
|
98
|
+
function internalClick() {
|
|
99
|
+
// Internal logic (e.g., ripple effect, analytics)
|
|
100
|
+
emit('action')
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<button @click="internalClick">
|
|
106
|
+
<slot />
|
|
107
|
+
</button>
|
|
108
|
+
</template>
|
|
109
|
+
|
|
110
|
+
<!-- Parent.vue - Use the custom event instead -->
|
|
111
|
+
<template>
|
|
112
|
+
<!-- Use @action, not @click, to avoid double handling -->
|
|
113
|
+
<BaseButton @action="handleAction">Submit</BaseButton>
|
|
114
|
+
</template>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Option 3: Use stopPropagation if needed
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<!-- BaseButton.vue - Stop event propagation when needed -->
|
|
121
|
+
<script setup>
|
|
122
|
+
const props = defineProps({
|
|
123
|
+
stopPropagation: Boolean
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
function handleClick(event) {
|
|
127
|
+
if (props.stopPropagation) {
|
|
128
|
+
event.stopPropagation()
|
|
129
|
+
}
|
|
130
|
+
// Internal handling...
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<button @click="handleClick">
|
|
136
|
+
<slot />
|
|
137
|
+
</button>
|
|
138
|
+
</template>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Using Additive Behavior Intentionally
|
|
142
|
+
|
|
143
|
+
The additive behavior can be useful for extending functionality:
|
|
144
|
+
|
|
145
|
+
```vue
|
|
146
|
+
<!-- EnhancedButton.vue - Leveraging additive listeners -->
|
|
147
|
+
<template>
|
|
148
|
+
<button
|
|
149
|
+
@click="trackClick"
|
|
150
|
+
@focus="trackFocus"
|
|
151
|
+
>
|
|
152
|
+
<slot />
|
|
153
|
+
</button>
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<script setup>
|
|
157
|
+
function trackClick() {
|
|
158
|
+
analytics.track('button_click')
|
|
159
|
+
// Parent's @click will also run - that's intentional!
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function trackFocus() {
|
|
163
|
+
analytics.track('button_focus')
|
|
164
|
+
}
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<!-- Parent.vue -->
|
|
168
|
+
<template>
|
|
169
|
+
<!-- Both analytics AND form submission happen -->
|
|
170
|
+
<EnhancedButton @click="submitForm">Submit</EnhancedButton>
|
|
171
|
+
</template>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Execution Order
|
|
175
|
+
|
|
176
|
+
```vue
|
|
177
|
+
<script setup>
|
|
178
|
+
// Component
|
|
179
|
+
function componentHandler() {
|
|
180
|
+
console.log('1. Component handler (first)')
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<template>
|
|
185
|
+
<button @click="componentHandler">Click</button>
|
|
186
|
+
</template>
|
|
187
|
+
|
|
188
|
+
<!-- Parent passes @click -->
|
|
189
|
+
<!-- Execution order:
|
|
190
|
+
1. componentHandler (defined in component)
|
|
191
|
+
2. parentHandler (passed as fallthrough)
|
|
192
|
+
-->
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Best Practices
|
|
196
|
+
|
|
197
|
+
1. **For UI components**: Use `inheritAttrs: false` and emit custom events
|
|
198
|
+
2. **For HOCs/wrappers**: Document that listeners are additive
|
|
199
|
+
3. **For analytics/tracking**: Leverage additive behavior intentionally
|
|
200
|
+
4. **Avoid side effects**: Don't assume your handler is the only one running
|
|
201
|
+
|
|
202
|
+
## References
|
|
203
|
+
|
|
204
|
+
- [Fallthrough Attributes - v-on Listener Inheritance](https://vuejs.org/guide/components/attrs.html#v-on-listener-inheritance)
|
|
205
|
+
- [Component Events](https://vuejs.org/guide/components/events.html)
|