skip to Main Content

I have a Form component which renders 3 InputContainer components. I want to access the value of the input element on the submit event of the form. Right now, my submitResource function logs the div which contains both label and input element.

If not necessary, I don’t want to emit any events with @change handler. The only way I’ve managed to access the input element is by console.log(titleRef.value.$el.querySelector('input')); but I think its not the right way to do it.

Thank you!

Form.vue

<template>
  <form @submit.prevent="submitResource">
    <InputContainer ref="titleRef" />
    <InputContainer ref="descriptionRef"/>
    <InputContainer ref="linkRef" />
    <BaseButton type="submit" class="submit">Add Resource</BaseButton>
  </form>
</template>

<script setup>
import BaseButton from './BaseButton.vue';
import InputContainer from './InputContainer.vue';
import { ref } from 'vue';

const titleRef = ref(null);
const descriptionRef = ref(null);
const linkRef = ref(null);

const submitResource = () => {
  console.log(titleRef.value.$el);
};
</script>

InputContainer.vue

<template>
  <div class="input-container">
    <label for="example">Example</label>
    <input
      type="text"
      id="example"
      name="example"
    />
  </div>
</template>

2

Answers


  1. You can use defineExpose to forward a ref from the child.

    InputContainer.vue:

    <template>
      <div class="input-container">
        <label for="example">Example</label>
        <input
          type="text"
          name="example"
          ref="inputRef"
        />
      </div>
    </template>
    <script setup>
    import { ref } from 'vue';
    const inputRef = ref(null);
    defineExpose({
        inputRef
    });
    </script>
    

    Then, you can access it in Form.vue.

    const submitResource = () => {
      const titleInputEl = titleRef.value.inputRef;
    };
    
    Login or Signup to reply.
  2. If it helps, the typical way to do this in Vue is through two-way binding with v-model. For this to work, you build your input components to receive a value through the :modelValue prop and have them emit an @update:modelValue event:

    Form.vue

    <template>
      <form @submit.prevent="submitResource">
        <InputContainer v-model="formData.title" />
        <InputContainer v-model="formData.description"/>
        <InputContainer v-model="formData.link" />
        <BaseButton type="submit" class="submit">Add Resource</BaseButton>
      </form>
    </template>
    
    <script setup>
    import BaseButton from './BaseButton.vue';
    import InputContainer from './InputContainer.vue';
    import { ref } from 'vue';
    
    const formData = ref({
      title: '',
      description: '',
      link: '',
    });
    
    const submitResource = () => {
      console.log(formData.value);
    };
    </script>
    

    InputContainer.vue

    <template>
      <div class="input-container">
        <label for="example">Example</label>
        <input
          type="text"
          id="example"
          :value="modelValue"
          @input="emit('update:modelValue', $event.target.value)"
        />
      </div>
    </template>
    

    This approach has several benefits, among them is:

    • loose coupling: parent does not need to know about internal structure of child
    • single source of reference: all data is represented in the data layer and reliably up to date (instead of floating around in the template) – this allows for efficient validation, debugging, etc.
    • readability: binding is apparent in the template instead of buried somewhere in the code
    • portability: pretty much all Vue components use this format, you can easily replace other input components with yours and vice versa
    • no surprises: if other developers work with your code, this is what they’ll expect
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search