Yovi Prasetyo
Yovi Prasetyo

Reputation: 212

Vue3 use v-model in Child Components

I just found out that in Vue3, v-model is not working responsively / reactively with child Component.

This code will update the username data

<template>
  <div>
    <input type="text" v-model="username" placeholder="Insert your username" />
    <p>{{ username }}</p>
  </div>
</template>

<script>
// Home.vue
export default {
  name: 'Home',
  data() {
    return {
      username: 'admin'
    }
  }
}
</script>

If I type something in the input, the username data will change too.

But, when I use Component like this example:

<template>
    <input type="text" :class="'input-text ' + additionalClass" :placeholder="placeholder" />
</template>

<script>
// InputText.vue
import { defineComponent } from "vue"

export default defineComponent({
    name: 'InputText',
    props: {
        placeholder: {
            type: String,
            default: ''
        },
        additionalClass: {
            type: String,
            default: ''
        }
    }
})
</script>

Then I updated my code to use the Component.

Note: The Component is registered successfully.

<template>
  <div>
    <input-text v-model="username" :placeholder="`Insert your username`" />
    <p>{{ username }}</p>
  </div>
</template>

<script>
// Home.vue
export default {
  name: 'Home',
  data() {
    return {
      username: 'admin'
    }
  }
}
</script>

When I type something, the username data not updated, different with the previous one.

Is there any solution or at least reference of what I'm trying to achieve?

Upvotes: 8

Views: 17245

Answers (6)

electroid
electroid

Reputation: 670

Starting vue 3.4 recommended approach is to use defineModel() macro. https://vuejs.org/guide/components/v-model

Based on your answer the code will looks like (with TypeScript types usage):

<script setup lang="ts">
const model = defineModel();

const props = defineProps<{  
  placeholder: string
  additionalClass: string
}>();
</script>

<template>
    <input 
        type="text" 
        v-model="model" 
        :class="'input-text ' + props.additionalClass"    
        :placeholder="props.placeholder"
    />
</template>

And in your parent component

<script setup lang="ts">

const textInput = ref('');

</script>
    
 <template>
    <SomeInput 
        v-model="textInput"
        placeholder="placeholder"
        additionalClass="some-class"
    />
</template>

Upvotes: 0

Mohsen_
Mohsen_

Reputation: 311

with Vue3 script setup syntax

<template>
  <input type="text" v-model="val" @input="handleInput">
</template>

<script setup>
import {ref, defineProps, defineEmits, watch} from 'vue'

const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
})
const emit = defineEmits(['update:modelValue'])

const val = ref('')

watch(() => props.modelValue, nVal => val.value = nVal, {
  immediate: true
})

const handleInput = () => emit('update:modelValue', val.value)
</script>

Upvotes: 16

Yom T.
Yom T.

Reputation: 9200

You can't expect v-model to implicitly update the underlying element for you. In other words, you'll still need to handle that within the component itself and expose modelValue as a prop for this to really work. Something like that:

<template>
  <input
    type="text"
    @input="onChanged"
    :value="modelValue"
    :class="'input-text ' + additionalClass"
    :placeholder="placeholder" />
</template>

<script>
  // InputText.vue
  import { defineComponent } from "vue"

  export default defineComponent({
    name: 'InputText',

    emits: ['update:modelValue'],

    props: {
      modelValue: String,
      placeholder: {
        type: String,
        default: ''
      },
      additionalClass: {
        type: String,
        default: ''
      }
    },

    setup(props, { emit }) {
      function onChanged(e) {
        emit('update:modelValue', e.currentTarget.value);
      }

      return {
        onChanged
      }
    }
  })
</script>

Upvotes: 12

Holtwick
Holtwick

Reputation: 1966

The VueUse helper useVModel can simplify handling v-model in a sub-component, see https://vueuse.org/core/usevmodel/#usage

<script lang="ts" setup>
import { useVModel } from '@vueuse/core'

const props = defineProps<{
  modelValue: string
}>()
const emit = defineEmits(['update:modelValue'])

const model = useVModel(props, 'modelValue', emit)
</script>

<template>
    <input type="text" v-model="model" />
</template>

Upvotes: 7

Asiful Islam Sakib
Asiful Islam Sakib

Reputation: 562

with OPTIONS API syntax

<template>
    <input type="text" v-model="val" @input="updateText" :value="modelValue">
</template>

<script scoped>
    export default {
        props: {
            modelValue: String,
        },
        methods: {
            updateText(event){
                this.$emit('update:modelValue', event.currentTarget.value)
            }
        }
    }
</script>

Upvotes: 2

Ilya Degtyarenko
Ilya Degtyarenko

Reputation: 1589

In vue2, this thing worked:

Your InputText component

<template>
    <input :value="value"
           @input="({target}) => $emit('input', target.value)"/>
</template>

<script>
    export default {
        props: {
            value: {}
        }
    }

Your parent component

<template>
    <inputText v-model="inputValue"/>
</template>

<script>
    export default {
        components: {inputText},
        data: () => ({
            inputValue: null
        })
    }
</script>

Upvotes: -1

Related Questions