Home > other >  How to properly work with v-model and the Composition API :value="modelValue" syntax? Conv
How to properly work with v-model and the Composition API :value="modelValue" syntax? Conv

Time:11-12

I have a useful little custom input I've been using in all of my Vue2 projects (allows you to customize with autofocus, autogrow and debounce), but now that I am working in Vue3, I've been trying to create an updated version.

I've not completed it yet, but I've come across some trouble with the Vue3's composition API :value="modelValue" syntax. In my CodeSandbox I have two inputs, one using the new syntax and the other just straight up using v-model. The later works, while the :value="valueInner" throws Extraneous non-props attributes errors.

What am I doing wrong here and how can I get this to work with that :value="modelValue" syntax?

Cheers!

Link to CodeSandbox

NOTE: I still need to add autogrow and add all the usecases in App.vue.

CInput

<template>
  <input
    ref="inputRef"
    data-cy="input-field"
    v-if="type !== 'textarea'"
    :disabled="disabled"
    :type="type"
    :placeholder="placeholder"
    :readonly="readonly"
    :required="required"
    :autofocus="autofocus"
    :debounce="debounce"
    :value="valueInner"
  />
  <!-- <input
    ref="inputRef"
    data-cy="input-field"
    v-if="type !== 'textarea'"
    :disabled="disabled"
    :type="type"
    :placeholder="placeholder"
    :readonly="readonly"
    :required="required"
    :autofocus="autofocus"
    :debounce="debounce"
    v-model="valueInner"
  /> -->
</template>

<script>
import { defineComponent, ref, onMounted, nextTick, watch } from "vue";

export default defineComponent({
  props: {
    /** HTML5 attribute */
    disabled: { type: String },
    /** HTML5 attribute (can also be 'textarea' in which case a `<textarea />` is rendered) */
    type: { type: String, default: "text" },
    /** HTML5 attribute */
    placeholder: { type: String },
    /** HTML5 attribute */
    readonly: { type: Boolean },
    /** HTML5 attribute */
    required: { type: Boolean },
    /** v-model */
    modelValue: { type: [String, Number, Date], default: "" },
    autofocus: { type: Boolean, default: false },
    debounce: { type: Number, default: 1000 },
  },
  emits: ["update:modelValue"],
  setup(props, { emit }) {
    const inputRef = ref(null);
    const timeout = ref(null);
    const valueInner = ref(props.modelValue);
    if (props.autofocus === true) {
      onMounted(() => {
        // I don't know we need nexttick
        nextTick(() => {
          inputRef.value.focus();
          // setTimeout(() => inputRef.value.focus(), 500);
        });
      });
    }
    watch(valueInner, (newVal, oldVal) => {
      const debounceMs = props.debounce;
      if (debounceMs > 0) {
        clearTimeout(timeout.value);
        timeout.value = setTimeout(() => emitInput(newVal), debounceMs);
        console.log(newVal);
      } else {
        console.log(newVal);
        emitInput(newVal);
      }
    });
    function emitInput(newVal) {
      let payload = newVal;
      emit("update:modelValue", payload);
    }
    // const onInput = (event) => {
    //   emit("update:modelValue", event.target.value);
    // };
    return { inputRef, valueInner };
  },
});
</script>

App.vue

<template>
  <CInput :autofocus="true" v-model.trim="inputValue1" />
  <CInput :autofocus="false" v-model.trim="inputValue2" />
  <pre>Input Value 1: {{ inputValue1 }}</pre>
  <pre>Input Value 2: {{ inputValue2 }}</pre>
</template>

<script>
import { ref } from "vue";
import CInput from "./components/CInput.vue";
export default {
  name: "App",
  components: { CInput },
  setup() {
    const inputValue1 = ref("");
    const inputValue2 = ref("");
    return { inputValue1, inputValue2 };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

CodePudding user response:

So far I have only built one App using Vue 3 Vite. I went with the <script setup> method only and stuck with it, this is how that looks when defining props:

<script setup>

import { onMounted } from 'vue'

const props = defineProps({
  readonly: { type: Boolean },
  required: { type: Boolean },
})

onMounted(() => {

  console.dir(props.readonly)

})

</script>

In the Vite setup, defineProps is globally registered, seems to be the only method that does not require an import, I would not know if this is true of any other compiler methods.

v-model in Vue3 pops out as modelValue not value, maybe you can defineProp the modelValue unless its there by default always?

There is some explanation here: https://v3.vuejs.org/guide/migration/v-model.html#_2-x-syntax

Hope this applies to you and helps.

CodePudding user response:

The full warning is:

[Vue warn]: Extraneous non-props attributes (modelModifiers) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

CInput has more than one root node (i.e., the <input> and the comment node), so the component is rendered as a fragment. The .trim modifier on CInput is normally passed onto root node's v-model, as seen in this demo. Since the actual code is a fragment, Vue can't decide for you where to pass on the modelModifiers prop, leading to the warning you observed.

However, declaring a modelModifiers prop to receive the modifiers is enough to resolve the problem:

// CInput.vue
export default {
  props: {
    modelModifiers: {
      default: () => ({})
    }
  }
}

demo

  • Related