feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
---
|
||||
title: Implement v-model Correctly in Render Functions
|
||||
impact: MEDIUM
|
||||
impactDescription: Incorrect v-model implementation breaks two-way binding with components
|
||||
type: best-practice
|
||||
tags: [vue3, render-function, v-model, two-way-binding]
|
||||
---
|
||||
|
||||
# Implement v-model Correctly in Render Functions
|
||||
|
||||
**Impact: MEDIUM** - When using `v-model` on components in render functions, you must manually handle both the `modelValue` prop and the `update:modelValue` event. Missing either part breaks two-way binding.
|
||||
|
||||
In templates, `v-model` is syntactic sugar that expands to a `modelValue` prop and an `update:modelValue` event listener. In render functions, you must implement this expansion manually.
|
||||
|
||||
## Task Checklist
|
||||
|
||||
- [ ] Pass `modelValue` as a prop for the bound value
|
||||
- [ ] Pass `onUpdate:modelValue` handler to receive updates
|
||||
- [ ] For named v-models, use the corresponding prop and event names
|
||||
- [ ] Use emit in child components to trigger updates
|
||||
|
||||
**Incorrect:**
|
||||
```javascript
|
||||
import { h } from 'vue'
|
||||
import CustomInput from './CustomInput.vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const text = ref('')
|
||||
|
||||
return () => h(CustomInput, {
|
||||
// WRONG: Missing the update handler
|
||||
modelValue: text.value
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
import { h } from 'vue'
|
||||
import CustomInput from './CustomInput.vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const text = ref('')
|
||||
|
||||
return () => h(CustomInput, {
|
||||
// WRONG: Wrong event name format
|
||||
modelValue: text.value,
|
||||
onModelValueUpdate: (val) => text.value = val
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
```javascript
|
||||
import { h, ref } from 'vue'
|
||||
import CustomInput from './CustomInput.vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const text = ref('')
|
||||
|
||||
return () => h(CustomInput, {
|
||||
// CORRECT: modelValue prop + onUpdate:modelValue handler
|
||||
modelValue: text.value,
|
||||
'onUpdate:modelValue': (value) => text.value = value
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Native Input Elements
|
||||
|
||||
For native inputs, use `value` and `onInput`:
|
||||
|
||||
```javascript
|
||||
import { h, ref } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const text = ref('')
|
||||
|
||||
return () => h('input', {
|
||||
value: text.value,
|
||||
onInput: (e) => text.value = e.target.value
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Named v-models (Multiple v-models)
|
||||
|
||||
```javascript
|
||||
import { h, ref } from 'vue'
|
||||
import UserForm from './UserForm.vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const firstName = ref('')
|
||||
const lastName = ref('')
|
||||
|
||||
return () => h(UserForm, {
|
||||
// v-model:firstName
|
||||
firstName: firstName.value,
|
||||
'onUpdate:firstName': (val) => firstName.value = val,
|
||||
|
||||
// v-model:lastName
|
||||
lastName: lastName.value,
|
||||
'onUpdate:lastName': (val) => lastName.value = val
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## v-model with Modifiers
|
||||
|
||||
Handle modifiers in the child component:
|
||||
|
||||
```javascript
|
||||
import { h, ref } from 'vue'
|
||||
import CustomInput from './CustomInput.vue'
|
||||
|
||||
// Parent - passing modifier
|
||||
export default {
|
||||
setup() {
|
||||
const text = ref('')
|
||||
|
||||
return () => h(CustomInput, {
|
||||
modelValue: text.value,
|
||||
'onUpdate:modelValue': (val) => text.value = val,
|
||||
modelModifiers: { trim: true, capitalize: true }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Child - handling modifier
|
||||
export default {
|
||||
props: ['modelValue', 'modelModifiers'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const handleInput = (e) => {
|
||||
let value = e.target.value
|
||||
|
||||
if (props.modelModifiers?.trim) {
|
||||
value = value.trim()
|
||||
}
|
||||
if (props.modelModifiers?.capitalize) {
|
||||
value = value.charAt(0).toUpperCase() + value.slice(1)
|
||||
}
|
||||
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
return () => h('input', {
|
||||
value: props.modelValue,
|
||||
onInput: handleInput
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JSX Syntax
|
||||
|
||||
```jsx
|
||||
import { ref } from 'vue'
|
||||
import CustomInput from './CustomInput.vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const text = ref('')
|
||||
const count = ref(0)
|
||||
|
||||
return () => (
|
||||
<div>
|
||||
{/* v-model on custom component */}
|
||||
<CustomInput
|
||||
modelValue={text.value}
|
||||
onUpdate:modelValue={(val) => text.value = val}
|
||||
/>
|
||||
|
||||
{/* v-model on native input */}
|
||||
<input
|
||||
value={text.value}
|
||||
onInput={(e) => text.value = e.target.value}
|
||||
/>
|
||||
|
||||
{/* Named v-model */}
|
||||
<Counter
|
||||
count={count.value}
|
||||
onUpdate:count={(val) => count.value = val}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating v-model-compatible Components
|
||||
|
||||
```javascript
|
||||
import { h } from 'vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: String
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
return () => h('input', {
|
||||
value: props.modelValue,
|
||||
onInput: (e) => emit('update:modelValue', e.target.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
- [Vue.js Render Functions - v-model](https://vuejs.org/guide/extras/render-function.html#v-model)
|
||||
- [Vue.js Component v-model](https://vuejs.org/guide/components/v-model.html)
|
||||
Reference in New Issue
Block a user