WBLord
WBLord

Reputation: 1025

TextArea Avoid mutating a prop directly since the value will be overwritten

I know that a similar question has already been dealt with on stackoverflow. But I could not put together a solution from the proposed one. I am very ashamed.

The essence is this: I have a component and another one inside it. The child component-VClipboardTextField is a ready-made configured text-area. I couldn't get the input out of there and I don't get an error when I try to enter it.

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"

enter image description here code tabs-item.vue

<template>
  <v-container fluid>
    <v-row align="center">
      <v-col cols="9">
        <v-card flat>
          <v-card-text>
            <h1>Request</h1>
            <v-container>
              <v-textarea v-model="message"
                          placeholder="Placeholder"
                          label="Request"
                          auto-grow
                          clear-icon="mdi-close-circle-outline"
                          clearable
                          rows="10"
                          row-height="5"


                          @click:clear="clearMessage"
              ></v-textarea>
              <v-textarea v-model="response"
                          placeholder="Placeholder"
                          label="Request2"
                          auto-grow
                          counter
                          rows="10"
                          row-height="5"
                          color="success"
              ></v-textarea>

              <VClipboardTextField ></VClipboardTextField>
              <VClipboardTextField isReadOnly></VClipboardTextField>
            </v-container>

            <v-row>

              <v-btn
                dark
                color="primary"
                elevation="12"
                justify="end"
                float-right
                @click="sendRequest"
              >
                Send Request
              </v-btn>
            </v-row>
          </v-card-text>
        </v-card>
      </v-col>

      <v-col cols="3">
        <schema-selector @changeSchema="onChangeSchema"></schema-selector>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: 'tabs-item',
  props: ['response'],
  data() {
    return {
      schema: String,
      message: '',
    }
  },
  methods: {
    sendRequest() {
      const message = {
        message: this.message,
        method: this.schema.state
      }
      this.$emit('sendRequest', message)
    },
    clearMessage() {
      this.message = ''
    },
    onChangeSchema(selectS) {
      console.log("get schema: ", selectS.state)
      this.schema = selectS

    }
  },

}
</script>

and child VClipboardTextField.vue

<template>
  <v-container>
    <v-tooltip bottom
               v-model="show">
      <template v-slot:activator="{ on, attrs }">
        <v-textarea
          v-model="message"
          :append-outer-icon="'mdi-content-copy'"
          :readonly="isReadOnly"
          auto-grow
          filled
          counter
          clear-icon="mdi-close-circle-outline"
          clearable
          label="Response message"
          type="text"
          @click:append-outer="copyToBuffer"
          @click:clear="clearMessage"
        ></v-textarea>

      </template>
      <span>Tooltip</span>
    </v-tooltip>
  </v-container>
</template>

<script>
export default {
  name: 'VClipboardTextField',
  props: {
    isReadOnly: Boolean,
    message : { type :String, default: "msg"}
  },
  data() {
    return {
      show: false,
    //  messageLocal: 'Response!',
      iconIndex: 0,
    }
  },
  methods: {
    copyToBuffer() {
      console.log("this: ", this)
      navigator.clipboard.writeText(this.message);
      this.toolTipChange()
      setTimeout(() => this.toolTipChange(), 1000)
    },
    clearMessage() {
      this.message = ''
    },
    toolTipChange() {
      if (this.show)
        this.show = false
    }
  }
}
</script>

I will be glad to see an example of the code that will explain how to correctly solve this problem without crutches! Thanks. enter image description here

Upvotes: 0

Views: 457

Answers (2)

Muluken Getachew
Muluken Getachew

Reputation: 1033

Instead of watching for change every time, you can only emit when there are changes.

In your tabs-item.vue

<VClipboardTextField v-model="message"></VClipboardTextField>

In your VClipboardTextField.vue component, you can receive the v-model input prop as a "VALUE" prop and assign it to a local data property. That way u can manipulate the local property and emit only when in it is changed!

<template>
  <v-container>
    <v-tooltip bottom v-model="show">
      <template v-slot:activator="{ on, attrs }">
        <v-textarea
          v-model="message"
          :append-outer-icon="'mdi-content-copy'"
          :readonly="isReadOnly"
          v-on="on"
          v-bind="attrs"
          auto-grow
          filled
          counter
          clear-icon="mdi-close-circle-outline"
          clearable
          label="Response message"
          type="text"
          @click:append-outer="copyToBuffer"
          @click:clear="clearMessage"
          @change="$emit('input', v)"
        ></v-textarea>
      </template>
      <span>Tooltip</span>
    </v-tooltip>
  </v-container>
</template>

<script>
export default {
  name: "VClipboardTextField",
  props: {
    isReadOnly: { type: Boolean },
    value: {
      type: String,
    },
  },
  data() {
    return {
      show: false,
      message: this.value,
    };
  },
};
</script>

Upvotes: 1

fevid
fevid

Reputation: 743

you cannot modify props in components, if the initial value of message is needed as placeholder (meaning the input might not be empty at the begining), you can store the data in message prop to another data variable and use that as v-model to the textarea. if there is no initial value for message, just use another data variable for textarea and emit an update for message in a watcher.

in code tabs-item.vue

<VClipboardTextField :message.sync="message"></VClipboardTextField>

and child VClipboardTextField.vue

<template>
  <v-container>
    <v-tooltip bottom
               v-model="show">
      <template v-slot:activator="{ on, attrs }">
        <v-textarea
          v-model="message_slug"
          :append-outer-icon="'mdi-content-copy'"
          :readonly="isReadOnly"
          auto-grow
          filled
          counter
          clear-icon="mdi-close-circle-outline"
          clearable
          label="Response message"
          type="text"
          @click:append-outer="copyToBuffer"
          @click:clear="clearMessage"
        ></v-textarea>

      </template>
      <span>Tooltip</span>
    </v-tooltip>
  </v-container>
</template>

<script>
export default {
  name: 'VClipboardTextField',
  props: {
    isReadOnly: Boolean,
    message : { type :String, default: "msg"}
  },
  data() {
    return {
      show: false,
    //  messageLocal: 'Response!',
      iconIndex: 0,
      message_slug: '',
    }
  },
  watch: {
    message_slug(x) {
      this.$emit('update:message', x)
    }
  },
}
</script>

It will bind the value to message_slug and update the message on parent component when it's value changes.

Upvotes: 1

Related Questions