Home > Blockchain >  Vue 3 <setup script>- computed properties are not reactive?
Vue 3 <setup script>- computed properties are not reactive?

Time:12-13

I've just started using the composition API and <script setup> block and attribute. So far I'm loving it (coming from vue 2) but I've had this weird issue I can't seem to get through.

I'm pulling a JSON object from my database that contains attributes (key:value pairs). Since the key is dynamic (think { color:"blue", size:"medium" }) I have to create a new object and add each attribute into it's own object specifying which is the key and which is the value. So { color:"blue"} becomes: { key: "color", value: "blue } which allows me to bind it to inputs in my <template>. When the page loads, it parses the attributes into the new object, and displays them in inputs.

enter image description here

If a user wants to add an attribute, they can click a button to create a new "row" for attributes. Problem is when they click the plus button, the attributes object is updated, but it doesn't reflect on the form (not reactive). I'm honestly not sure why. Here is my <script>:

<script setup>

import { computed, reactive } from "vue";
import { getProductByID } from "@/composables/products";

const props = defineProps({
  productid: String,
});

const { product, error, loadProduct} = getProductByID(props.productid);
loadProduct();

const attributes = reactive({
    english: computed(() => {
      if (product.value.attributes && Object.keys(product.value.attributes).length > 0) {
      
        let newValues = [];

        for (const [key, value] of Object.entries(product.value.attributes)) {
          let s = {};
          s["key"] = key;
          s["value"] = value;
          newValues.push(s);
        }
        return newValues;
      }
      return {};
    })
  });

NOTE: the product returned from the composable is wrapped in ref{}:

import axios from "axios";
import { ref } from "vue";

const product = ref({});
const error = ref(null);
...
return {product, error, loadProduct}

The attributes are bound to the input properly:

<div id="attributes">
  <!-- English Attributes -->
  <div>
    <div>
      <label>Attributes (property : value) </label>
      <button @click.prevent="addAttributeInput">
        <span >
          <i ></i>
        </span>
      </button>
    </div> 
    <div v-for="(attribute, index) in attributes.english" :key="index">
      <div>
        <input v-model="attributes.english[index]['key']" placeholder="attribute" />
      </div>
      <div >
        <span>:</span>
      </div>
      <div>
        <input v-model="attributes.english[index]['value']" placeholder="value" />
      </div>
      <button @click.prevent="removeAttributeInput(key)">
        <span >
          <i ></i>
        </span>
      </button>
    </div>
  </div>
</div>

If the user wants to add (or remove) an attribute, there's a button that adds a blank object to the attributes.english object:

function addAttributeInput() {
  attributes.english.unshift({"key":"", "value":""});
}

function removeAttributeInput(index) {
  attributes.english.splice(index, 1);
}

When I click these buttons, it does modify the attributes.english object (I see it in the vue tools in browser) but it doesn't update the form reactively. Does wrapping the attributes object with reactive() not make it reactive?

CodePudding user response:

The problem with your code is that you can't mutate computed() props by the usage of a v-model (if it isn't writable). Therefor, this can't work.

Of course, there are multiple options to solve your problem. I made a Stackblitz to show how to do it with the usage of a watch() which sets the value of a ref().

It depends on the use-case but it could be better to make use of a writable computed.

  • Related