skip to Main Content

In Vue 3, when emitting an event from child to parent to update refs and subsequently refresh the props of the child the form control flow breaks i.e. tabindex or [tab]ing between fields doesn’t work. My code is roughly setup as follows:

Parent.vue

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const connection = ref(null)

const change = (updated) => {
    connection.value = updated
}
</script>
<template>
    <Child :connection="connection" @update="change" />
</template>

Child.vue

<script setup>
import { ref } from 'vue'

const props = defineProps({
    connection: Object,
})

const emit = defineEmits(['update'])

const a = ref(props.connection?.a)
const b = ref(props.connection?.b)
const c = ref(props.connection?.c)

const update = () => {
    emit('update', {
        a: a.value,
        b: b.value,
        c: c.value,
    })
}
</script>
<template>
    <input v-model="a" @blur="update" />
    <input v-model="b" @blur="update" />
    <input v-model="c" @blur="update" />
</template>

I’m guessing due to it being an object that it breaks the the flow as it re-renders the whole child and loses focus. Question is how can I update the object and keep the focus the same?

Side note: I don’t want to emit individual model value events like below because I don’t want the parent to have to deal with every property since I’m creating several distinct Child vues for different forms.

<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

This would lead to a parent like this and I’ll end up having to handle a-z

...
<Child 
    v-model:a="connection.a"  
    v-model:b="connection.b"
    v-model:c="connection.c"
...

2

Answers


  1. Chosen as BEST ANSWER

    The answer from @boussadjra-brahim was useful and led me to this solution. I preferred the code below as I could transform data going back and forth between parent and child as illustrated by the split-join on ref c.

    <script setup>
    import { computed } from 'vue'
    
    const props = defineProps({
      modelValue: Object,
    })
    
    const a = ref(props.modelValue?.a)
    const b = ref(props.modelValue?.b)
    const c = ref(props.modelValue?.c?.join(', '))
    
    const emit = defineEmits(['update:modelValue'])
    
    const update = () => {
      props.modelValue.a = a.value
      props.modelValue.b = b.value
      props.modelValue.c = c.value?.split(',')
        .map((s) => s.trim())
        .filter(Boolean) || []
    
      emit('update:modelValue', props.modelValue)
    }
    </script>
    <template>
      <input v-model="a" @blur="update" />
      <input v-model="b" @blur="update" />
      <input v-model="c" @blur="update" />
    </template>
    

    Parent component :

    <script setup>
    import { ref } from 'vue'
    import Child from './Child.vue'
    const connection = ref({})
    
    const update = (updated) => {
      connection.value = updated
      // some other ref mods
    }
    
    </script>
    <template>
        <Child v-model="connection" update:model-value="update"  />
    </template>
    

  2. Try to simplify the Child component state with a writable computed that get the prop values and emit the new values when it’s mutated :

    <script setup>
    import { computed } from 'vue'
    
    const props = defineProps({
      modelValue: Object,
    })
    
    const emit = defineEmits(['update:modelValue'])
    
    const model = computed({
      get: () => props.modelValue,
      set: (val) => {
        console.log(val)
        emit('update:modelValue', val)
      }
    })
    </script>
    <template>
      <input v-model="model.a" @blur="update" />
      <input v-model="model.b" @blur="update" />
      <input v-model="model.c" @blur="update" />
    </template>
    
    

    Parent component :

    <script setup>
    import { ref } from 'vue'
    import Child from './Child.vue'
    const connection = ref({})
    
    </script>
    <template>
        <Child v-model="connection"  />
    </template>
    

    LIVE DEMO

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search