4.9 KiB
4.9 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | ||||
|---|---|---|---|---|---|---|---|---|
| Implement v-model Correctly in Render Functions | MEDIUM | Incorrect v-model implementation breaks two-way binding with components | best-practice |
|
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
modelValueas a prop for the bound value - Pass
onUpdate:modelValuehandler to receive updates - For named v-models, use the corresponding prop and event names
- Use emit in child components to trigger updates
Incorrect:
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,
});
},
};
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:
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:
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)
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:
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
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
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),
});
},
};