Vue not triggering computed property sets

As per the guidelines, I should be able to utilize computed properties as v-model in Vue by defining get/set methods. However, in my scenario, it isn't functioning as expected:

export default{

    template: `
      <form class="add-upload" @submit.prevent="return false">
        <label><input type="checkbox" v-model="options.test" /> test </label>
      </form>
    `,

    computed: {

      options: {

        get(){
          console.log('get');
          return {test: false};
        },

        set(value){
          console.log('set');
        },

      },

    }

}

Although the get method gets called when the component is displayed, the set method is not triggered when I check/uncheck the input.

Answer №1

Modification: Upon reviewing the comments indicating your reliance on localstorage, I recommend adopting the Vuex methodology and utilizing a persistence library for managing localstorage. (https://www.npmjs.com/package/vuex-persist) By doing so, your localstorage will remain integrated with your app without the need to constantly deal with getItem/setItem operations.

From the way you've approached it, it seems you have deliberate reasons for opting for a computed property rather than a data property.

The issue arises because your computed property returns an object that is only defined within the get handler. No matter what you attempt, manipulating that object within the set handler proves impossible.

The get and set should be associated with a shared reference. This could be a data property as suggested by many, or a central source of truth in your application (like a Vuex instance).

This setup ensures that your v-model functions seamlessly alongside the set handler of your computed property.

Here's a functional example illustrating this concept:

Using Vuex

const store = new Vuex.Store({
  state: {
    // Predefined options object in the store for Vue to recognize its structure
    options: {
      isChecked: false
    }
  },
  mutations: {
    setIsCheck(state, payload) {
      state.options.isChecked = payload;
    }
  }
});

new Vue({
  store: store,
  el: "#app",
  computed: {
    options: {
      get() {
        // Returning the options object as depicted in your code snippet
        return this.$store.state.options;
      },
      set(checked) {
        // Using the checked property from the input to commit a Vuex mutation that updates the state
        this.$store.commit("setIsCheck", checked);
      }
    }
  }
})
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}
<div id="app">
  <h2>isChecked: {{ options.isChecked }}</h2>
  <input type="checkbox" v-model="options.isChecked" />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a6d0d3c3dee69488968896">[email protected]</a>"></script>

Using a data property

new Vue({
  el: "#app",
  data: {
    options: {
      isChecked: false
    }
  },
  computed: {
    computedOptions: {
      get() {
        return this.options;
      },
      set(checked) {
        this.options.isChecked = checked;
      }
    }
  }
})
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}
<div id="app">
  <h2>isChecked: {{ computedOptions.isChecked }}</h2>
  <input type="checkbox" v-model="computedOptions.isChecked" />
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Your approach may seem unconventional from my perspective but, once again, there must be valid motivations behind it.

Answer №2

Explained simply in code, computed properties rely on other data/reactive variables. They become reactive only when the reactive properties change values and if the same property is used to compute other computed properties.

To achieve this, we need to set and get values using setter and getter methods.

new Vue({
  el: '#app',
  data: {
    message: 'Use computed property on input',
    foo:0,
    isChecked:true
  },
  computed:{
   bar:{
    get: function(){
        return this.foo;
    },
   set: function(val){
     this.foo = val;
    }
   },
   
    check:{
    get: function(){
        return this.isChecked;
    },
   set: function(val){
     this.isChecked = val;
    }
   }
  }
})
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <p>{{ message }} Text</p>
  <input type="text" v-model="bar" /> 
  {{bar}}

<br/>
 <p>{{ message }} Checkbox</p>
    <input type="checkbox" v-model="check" /> 
    
    {{check}}
</div>

Answer №3

Instead of using a computed getter/setter, it is recommended to utilize a local data property that is initialized with the desired value from the localStorage. By implementing a deep watcher (which monitors changes in any subproperty), you can then update the localStorage when any changes occur. This approach enables you to continue utilizing v-model with the local data property while being able to track modifications on the object's subproperties.

Follow these steps:

  1. Define a local data property named options and initialize it with the current value retrieved from localStorage:
export default {
  data() {
    return {
      options: {}
    }
  },
  mounted() {
    const myData = localStorage.getItem('my-data')
    this.options = myData ? JSON.parse(myData) : {}
  },
}
  1. Create a watch for the data property (options) with deep=true and define a handler function that updates the localStorage with the new value:
export default {
  watch: {
    options: {
      deep: true,
      handler(options) {
        localStorage.setItem('my-data', JSON.stringify(options))
      }
    }
  },
}

Check out the demo here

Answer №4

Seems like the issue lies both in the presence of options and the return value of the getter.

You can attempt the following solution:

let options;

try {
  options = JSON.parse(localStorage.getItem("options"));
}
catch(e) {
  // default values
  options = { test: true };
}

function saveOptions(updates) {
  localStorage.setItem("options", JSON.stringify({ ...options, ...updates }));
}

export default{
  template: `
    <form class="add-upload" @submit.prevent="return false">
      <label><input type="checkbox" v-model="test" /> test </label>
    </form>`,
  computed: {
    test: {
      get() {
        console.log('get');
        return options.test;
      },
      set(value) {
        console.log('set', value);
        saveOptions({ test: value });
      },
    },
  }
}

I hope this resolution is beneficial to you.

Answer №5

Unsure if there exists a computed set method that would be suitable in this scenario, however, there are several alternative approaches to tackle the issue.

If you require a single getter for modifying the data, utilizing an event-driven approach for setting the data could be beneficial. The following method is preferred:

export default {
  template: `
      <form class="add-upload" @submit.prevent="">
        <label for="test"> test </label>
        {{options.test}}
        <input id="test" type="checkbox" v-model="options.test" @input="setOptions({test: !options.test})"/>
      </form>
    `,
  data() {
    return {
      optionsData: {
        test: false
      }
    }
  },
  computed: {
    options: {
      get() {
        return this.optionsData;
      },
    },
  },
  methods: {
    setOptions(options) {
      this.$set(this, "optionsData", { ...this.optionsData, ...options })
    }
  }
}

If there is no need for complex logic within the get/set functions, using the data option directly can suffice

export default {
  template: `
      <form class="add-upload" @submit.prevent="">
        <label for="test"> test </label>
        {{options.test}}
        <input id="test" type="checkbox" v-model="options.test" />
      </form>
    `,
  data() {
    return {
      options: {
        test: false
      }
    }
  }
}

Additionally, there is the possibility of implementing get/set functions for each property individually

export default {
  template: `
      <form class="add-upload" @submit.prevent="">
        <label for="test"> test </label>
        {{test}}
        <input id="test" type="checkbox" v-model="test" />
      </form>
    `,
  data() {
    return {
      optionsData: {
        test: false
      }
    }
  },
  computed: {
    test: {
      get() {
        return this.optionsData.test;
      },
      set(value) {
        this.optionsData.test = value
      }
    },
  },
}

Answer №6

Vue computed properties do not automatically become reactive when you return a plain object and assign to a property within the computed property, as the setter will not trigger.

To address this issue, you need to solve two problems. One solution is to store a reactive version of your computed property value using Vue.observable(). The second problem may involve hooking into the setter for specific reasons, such as performing side-effects. In that case, you should watch the value for changes using vm.$watch().

Based on these assumptions, here's how I would write that component:

export default {
  template: `
      <form class="add-upload" @submit.prevent="return false">
        <label><input type="checkbox" v-model="options.test" /> test </label>
      </form>
    `,
  computed: {
    options(vm) {
      return (
        vm._internalOptions ||
        (vm._internalOptions = Vue.observable({ test: false }))
      )
    },
  },
  watch: {
    "options.test"(value, previousValue) {
      console.log("set")
    },
  },
}

If you need to trigger side-effects based on changes in the options object, you can deeply watch it by setting deep: true in the watch option. However, keep in mind that the object must be reactive, which can be achieved by using Vue.observable() or defining it in the data option.

export default {
  watch: {
    options: {
      handler(value, previousValue) {
        console.log("set")
      },
      deep: true,
    },
  },
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Choose Status Menu DiscordJS version 14

Is there a way to get help with changing the bot's status if it's not working properly? The value of the variable "statuses" is set as status, but the status itself does not change. Using client.user.setStatus('dnd'); can sometimes work ...

Transforming unprocessed string information with a set position-dependent format into a structured format such as JSON

Here is the scenario I am dealing with: The input format consists of a string with a fixed total length, where each set of fixed positions represents a different value. For example, if the input is "ABCDE12345", position 1 to 3 ("ABC" ...

Else statement malfunctioning with Alert() function

I have noticed an issue with my user input field. Even when I enter a valid genre name, the alert prompt still appears saying it is not a valid genre. This occurs after entering both valid and invalid inputs. For example, if the user enters "horror," whic ...

Error encountered: WebResource.axd is not found on the .net webforms website when accessed through Cloudfront

I am facing a challenge with migrating an existing .NET webforms site to work behind Cloudfront as all the webforms are breaking. Upon further investigation, it has been discovered that the site appears fine, but the webforms are breaking because the < ...

Data sent as FormData will be received as arrays separated by commas

When constructing form data, I compile arrays and use POST to send it. Here's the code snippet: let fd = new FormData(); for (section in this.data.choices) { let key = section+(this.data.choices[section] instanceof Array ? '[]' : '& ...

MongoDB has encountered an issue where it is unable to create the property '_id' on a string

Currently, I am utilizing Node.js and Express on Heroku with the MongoDB addon. The database connection is functioning correctly as data can be successfully stored, but there seems to be an issue with pushing certain types of data. Below is the database c ...

Removing leading zeros from numeric strings in JSON data

I am facing an issue with my jQuery-based JavaScript code that is making an Ajax call to a PHP function. updatemarkers.xhr = $.post( ih.url("/AjaxSearch/map_markers/"), params).done( function(json) { <stuff> } The PHP function returns the follo ...

Retrieve data from a JSON file using Ajax and jQuery

I have a JSon file that contains information about some matches: [{ "id": "2719986", "orario": "00:30", "casa": "Bahia", "trasferta": "Internacional" } , { "id": "2719991", "orario": "02:00", "casa": "Palmeiras", "trasferta": "Botafogo RJ" }] I'v ...

Can Facebox's settings be adjusted during runtime? If so, how can this be done?

Can Facebox settings be accessed and customized? For instance, I am interested in setting the location of the loading image dynamically as shown on line 4: <script type="text/javascript" src="<?php echo base_url(); ?>media/facebox/facebox.js" > ...

Configuring Braintree Client with JS v3 - encountering a null payment_method_nonce causing issues with form submission

I have successfully integrated Braintree into my Laravel 5.2 app using JS v2 client setup, but now I want to upgrade to v3. I have customized the code from the docs as follows: <form id="checkout-form" action="/checkout" method="post"> <div id= ...

Update the class of the element that is currently selected using jQuery and the `this` keyword

Is there a way to change the class on hover only for the current element using 'this'? The code I have changes classes for all elements, but I need it to work individually. Here is the code snippet I'm currently using: https://codepen.io/ky ...

Using Bootstrap4 to merge rows into a single column or apply rowspan in Bootstrap

Hey there, I have a specific requirement that I need help with. Check out the image here. I want to enable the LCM information box when the LCM checkbox is checked. Below is my code: <div class="panel-body "> <div class="c ...

The localStorage is currently being updated, however there seems to be an issue with the output where false is being mistakenly interpreted

My goal is to show the <BR/> component when the value is true, otherwise display the <Nothing/> component. Despite the value being false, the <BR/> is still appearing for some unknown reason. PC.js code: import React,{useContext, useStat ...

Achieving Dynamic Center Alignment of Filtered Node in SVG Using D3

I am currently implementing filter functionality for my d3 graph. The goal is to allow users to search for a specific node by label or ID, then re-render the graph to display the entire structure with the filtered node positioned at the center of the SVG e ...

Using Function Call to Generate Components in React

Being tired of repeatedly defining states to render Components based on conditions, I often find myself just wanting to display notifications or alerts. My current dilemma is figuring out how to render a component by invoking a function from within that co ...

Issue with Counting Digg Button Clicks

I can't figure out why the digg button counter isn't working. I followed the instructions but... The website in question is: . I implemented the code exactly as explained here: But the counter remains at 0. Has anyone encountered a similar iss ...

"Headers cannot be set once they have been sent to the client... Error handling for unhandled promise rejection

Having trouble with cookies in the header - specifically, encountering an error at line number 30. The error message reads: "Cannot set headers after they are sent to the client." Additionally, there is an UnhandledPromiseRejectionWarning related to a prom ...

SQL inputs with information that updates automatically / autofill SQL input boxes

Is it feasible to create a webpage that can connect to an SQL database, retrieve information from a table, and display it in a text box based on the input provided in another text box? Essentially, I am looking for a way to enable autofill functionality. I ...

Show notifications after being redirected to a new page

My goal is to submit a form and have the page redirected to a different URL upon submission. Currently, everything works as expected, but there is an issue with the timing of the toast message. The toast message appears immediately after the user clicks th ...

Updating Vue.js Component Data

I have set up a basic Example Component which is bound to a Vue Instance as shown below: <template> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> < ...