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

Discover the technique of accessing HTML/CSS toggle switch value in Golang

I am attempting to incorporate a toggle switch into my webpage. I followed this specific tutorial for guidance: w3schools.com Currently, I have set up my HTML file with a button and the toggle switch. Additionally, I configured my web server in Go to lis ...

When using <body onload=foo()>, the function foo will not be executed

Having trouble with this code - it seems like initialize() is not being called. Check out the code here Any insight into why this might be happening? ...

Navigating through the img src using JavaScript

Currently, I am working on a task that involves the following code snippet: <input type="file" id="uploadImage" name="image" /> <input type="submit" id="ImageName" name="submit" value="Submit"> My goal is to have the path of the selected imag ...

Finding the file in a separate directory within the src path

In my projects directory, I have a folder named projects which contains both a game folder and an engine folder. Inside the engine folder, there is an engine.js file. My issue is that I want to access this engine.js file from my game.html file located in a ...

Enhance Your jQuery Skills: Dynamically Apply Classes Based on URL Like a Pro!

Here is an example of HTML code for a progress meter: <div class="col-md-3" style="margin-left: -20px;"> <div class="progress-pos active" id="progess-1"> <div class="progress-pos-inner"> Login </div> </di ...

How can I only replace every second occurrence in a JS string?

Looking for help with JavaScript: "a a a a".replace(/(^|\s)a(\s|$)/g, '$1') I thought the result would be '', but it's showing 'a a' instead. Can someone explain what I'm missing? To clarify, I want to r ...

Tips on avoiding duplicate selection of checkboxes with Vue.js

Recently delving into vue.js, I encountered a challenge with multiple checkboxes sharing the same value. This resulted in checkboxes of the same value being checked simultaneously. How can this issue be resolved? var app = new Vue({ el: '#app&apo ...

Is there a way to display only the specific child div within the parent div using JavaScript without affecting the others?

   **** Sorry for the lengthy title **** Today I encountered a problem that I would like to discuss with all of you. When I click on a "COMMENT" button, instead of triggering JavaScript code to display a CHILD.div inside the corresponding ...

Pass a URL string to a different script using Node.js

Currently, I am delving into the world of Node.js, utilizing Express with Jade and Mongoose as my primary tools. Originating from a background in PHP, transitioning to Python, and embracing MVC principles through Django, my aspiration is to create a multip ...

Swap the Text for a Button

I've been searching for a solution to this problem, but all I seem to find online is advice on how to "replace button text." What I'm trying to achieve is to have text change into a button when hovered over. I've attempted using fadeIn/fade ...

What's the best way to organize a list while implementing List Rendering in VueJS?

Currently, I am working on List Rendering in Vue2. The list is rendering correctly, but it appears ordered based on the data arrangement in the array. For better organization, I need to sort each item alphabetically by title. However, I am facing difficult ...

When a row is clicked, retrieve the data specific to that row

I have implemented a data-grid using react-table where I pass necessary props to a separate component for rendering the grid. My issue is that when I click on a particular row, I am unable to retrieve the information related to that row using getTrProps. ...

Exploring the intricacies of extracting nested JSON data in TypeScript

Can someone help me with this issue? https://example.com/2KFsR.png When I try to access addons, I only see [] but the web console indicates that addons are present. This is my JSON structure: https://example.com/5NGeD.png I attempted to use this code: ...

"Discover the best way to retrieve data within a Vuex store during the mounted or created lifecycle functions

After storing data in Vuex, I encountered an issue when trying to access my Vuex store after changing routes (components). When inside the mounted function, it shows me an error and fails to return the data. How can I resolve this? I am aware that using a ...

Encountering HTML content error when attempting to log in with REST API through Express on Postman

I'm currently in the process of developing a basic login API using Node.js and Express. However, I've encountered an error when testing with Postman: <!DOCTYPE html> <html lang="en"> <head> <meta charse ...

This JavaScript function is designed to strip HTML elements from a given

"p" tags are disappearing after running the javascript, but they are needed for structuring purposes. Is there a way to retain the html tags in the hidden/secondary text block even after the javascript manipulation? function AddReadMore() { //This lim ...

Is there a way for me to extract the document title from firebase?

I have been trying to use this component to retrieve data, but I am encountering an issue where I cannot fetch the name of the document "sampleCloudFunction" under CloudFunctionMonitor (see image) from the database and display it. Using docRef.id does not ...

Tips for validating and retrieving data from a radio button paired with an input box in reactjs

I'm diving into the world of React and facing a challenge with multiple radio buttons that have associated input fields, like in this image: https://i.stack.imgur.com/Upy3T.png Here's what I need: If a user checks a radio button with a ...

Oops! An error has occurred: The requested method 'val' cannot be called on an undefined object

I am struggling with this issue. This is the code that I am currently working on: http://jsfiddle.net/arunpjohny/Jfdbz/ $(function () { var lastQuery = null, lastResult = null, // new! autocomplete, processLocation = function ...

Encountering an unusual reactivity problem involving Firebase (Firestore) when using Vue.js and Vuefire

I'm facing a strange issue and I'm completely stuck. Here is the component in question: <template> <v-card elevation="0"> <h2>Accounts</h2> <v-simple-table fixed-header height="300px"> <template v ...