Vue Array Proxy Class Fails to Trigger Reactivity

My custom Array extension has a feature where it intercepts changes made to its properties using Proxy, which is returned from the constructor. However, when used in a Vue component, it encounters issues. For example, when a filter is added, the display doesn't update and the watchEffect doesn't fire as expected. Although the value gets added correctly, there seems to be a conflict between my proxy and Vue's wrapper. I suspect this might be related to how proxies are handled internally within Vue.

Collection.js

export class MyCollection extends Array {
  constructor(data) {
    super();
    this.add(data);
    return new Proxy(this, {
      set(target, prop, value) {
        target[prop] = value;
        if (prop === 'filters') {
          const add = []
          target.records.forEach((item) => {
            if (item.id === target.filters) {
              add.push(item)
            }
          })
          target.add(add);
        }
        return true;
      }
    })
  }
  
  addFilters() {
    this.filters = 1
  }
  
  add(items) {
    this.length = 0;
    items = Array.isArray(items) ? items : [items];
    this.records = items;
    console.log('here', this.records, this);
    items.forEach((item) => this.push(item))
  }
}

App.vue

<script setup>
  import {watchEffect, computed, ref, toRaw} from "vue";
  import {MyCollection} from "./Collection.js";
  
  const collection1 = $ref(new MyCollection([{id: 1, display: 'one'}, {id: 2, display: 'two'}]));
  
  watchEffect(() => {
    console.log("wow", collection1);
  });
  
  const onClickUpdate1 =() =>  {
    collection1.addFilters();
  }
</script>

<template>
  <div>
    Collection 1
    <button @click='onClickUpdate1'>
      Add Filter
    </button>
  </div>
  <div v-for="item in collection1" :key="item.id">
    {{item.display}}
  </div>
</template>

Answer №1

Check out this revised version of your code.

I have made some alterations:

  1. Switched to script setup for improved readability
  2. Shifted from computed to reactive using the $red syntax, as mentioned in reactivity transform
  3. You were including items with name: 'three' and exhibiting item.display. I adjusted it to add with display: 'three'.

It is functioning now, and I believe the change from computed to reactive has had an impact, although I will delve deeper into this to confirm. I will update the response accordingly once I learn more.

Answer №2

I have come up with a solution, but I also encountered what seems to be a bug in Vue, which I have already reported. The key change I had to make was to call the receiver's method instead of the target's method in the set trap of MyCollection. For more details, please refer to the issue report.

MyCollection.js

export class MyCollection extends Array {
  constructor(data) {
    super();
    this.add(data);
    return new Proxy(this, {
      set(target, prop, value, receiver) {
        target[prop] = value;
        if (prop === 'filters') {
          const add = [];
          target.records.forEach((item) => {
            if (item.id === target.filters) {
              add.push(item);
            }
          });
          // IMPORTANT: Had to use receiver here instead of target
          receiver.add(add);
        }
        return true;
      }
    });
  }
  
  addFilters() {
    this.filters = 1;
  }
  
  add(items) {
    this.length = 0;
    items = Array.isArray(items) ? items : [items];
    this.records = items;
    items.forEach((item) => this.push(item));
  }
}

The second issue that I faced, which might be a bug, is the inability to use a computed method for this. However, I found a workaround using ref and watchEffect to achieve the desired functionality.

App.vue

<script setup>
  import {watchEffect, computed, ref, toRaw} from "vue";
  const props = defineProps({
    options: {
      type: Array,
      default: [{id: 1, display: 'one'}, {id: 2, display: 'two'}]
    }
  })
  import {MyCollection} from "./Collection.js";
  
  const collection1 = ref(null);
  const collection2 = computed(() => new MyCollection(props.options))
  
  // Workaround for not being able to use computed
  watchEffect(() => {
    collection1.value = new MyCollection(props.options)
  });
  watchEffect(() => {
    console.log("collection1", collection1.value.length);
  });
  // THIS WILL NOT FIRE WHEN ADD FILTER IS CLICKED
  watchEffect(() => {
    console.log("collection2", collection2.value.length);
  });
  
  const onClickUpdate1 = () =>  {
    collection1.value.addFilters();
    collection2.value.addFilters();
  }
</script>

<template>
  <div>
    <button @click='onClickUpdate1'>
      Add Filter
    </button>
  </div>
  <div style="display: flex">
    <div style="margin-right: 1rem;">
      Collection 1
      <div v-for="item in collection1" :key="item.id">
        {{item.display}}
      </div>
    </div>
    <div>
      Collection 2
      <div v-for="item in collection2" :key="item.id">
        {{item.display}}
      </div>
    </div>
  </div>
</template>

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

"Keep a new tab open at all times, even when submitting a form in

I have a form with two submit buttons - one to open the page in a new tab (preview) and another for regular form submission (publish). The issues I am facing are: When I click the preview button to open in a new tab, if I then click the publish button a ...

Strangely unusual issues with text input boxes

So I've set up two textareas with the intention of having whatever is typed in one appear simultaneously in the other. But despite my best efforts, it's not working as expected. Here's the code snippet: <script> function copyText () { ...

Steps for implementing AJAX to display a success function and update database results in real-time

I'm struggling with allowing my AJAX call to send data to my PHP file and update the page without a reload. I need the success message to display after approving a user, but their name doesn't move on the page until I refresh. The goal is to app ...

Struggling with setting values in AngularJS?

Can you assist me in passing data from: (Page 1 -> Module 1 -> Controller 1) to (Page 2 -> Module 2 -> Controller 2) For example: Module reports - Controller ReportsCtrl //Page 1 <html lang="en" class="no-js" ng-app="reports"> <a ng-href="../n ...

Loop through an array of elements in JavaScript and retrieve the element with the largest pixel size

I am facing a challenge with elements positioned absolutely on a web page. My goal is to iterate over these elements and identify the one with the highest top value. I attempted two different approaches: const elems = document.querySelectorAll('.ind ...

Unveil Secret Divs with a Click

I am in search of a way to display a hidden div when I click on a specific div, similar to the expanding images feature in Google's image search results. I have made progress with my limited knowledge of javascript, as shown in this CodePen: http://co ...

I'm seeing a message in the console that says "Form submission canceled because the form is not connected." Any idea why this is happening?

For the life of me, I can't figure out why this code refuses to run the handleSubmit function. Essentially, the form is supposed to take an input and execute the handleSubmit function upon submission. This function then makes a POST request to an API ...

css based on the current time in the United States

I have a working code that currently reads the user's computer time, but I specifically need to get the hours from the USA regardless of the user's location. The CSS should be applied based on USA time. <script type="text/javascript"> dat ...

Encountering an issue in a Vue console where the $ref is returning null and prompting an error message

It's puzzling why I keep encountering a console error in Vue that says "cannot read null of a $ref". Despite having the correct HTML template and adding logic to the script tag as needed, I'm still facing this issue - Cannot read properties of nu ...

`Zooming and scrolling feature within a masked image`

I'm struggling to achieve a scrolling zoom effect similar to the website mentioned below, but I can't seem to get it to fully zoom. Additionally, when I try to zoom in on a clipped shape or text while scrolling, the entire div ends up scrolling ...

Having trouble with Angular UI Select functionality?

I have integrated the angular ui select library from https://github.com/angular-ui/ui-select into my project. Instead of using the traditional select element, I am now utilizing the ui-select directive. This is a snippet of my code: <select class=" ...

Tips for updating one-time binding data in AngularJS

I am currently facing an issue with a div that displays details such as mobile number, name etc. in the format: {{::mobilenumber}}, {{::name}} Within this div, there is a button that when clicked should populate the same values in a new form However, des ...

What is the best way to retrieve the value of a custom attribute from a dropdown list using either JavaScript or jQuery?

I have a dropdown list on a web form and I need to have a 'hidden' variable for each item in the list that can be retrieved using the onchange event on the client side. To achieve this, after databinding the dropdownlist, I am setting a custom at ...

Error in VueJS: Computed property not updating as expected due to lacking a setter function (which is

I've created a Wrapper component that wraps a third-party component. Here's how it looks: <template> <custom-element v-model="computedProperty" > </custom-element> </template> <script> export default { ...

Do you find encodeURIComponent to be extremely helpful?

I'm still puzzled about the benefit of using the JS function encodeURIComponent to encode each component of an http-get request when communicating with the server. After conducting some experiments, I found that the server (using PHP) is able to rece ...

Loop over elements retrieved from Firebase using ng-repeat

I am currently attempting to iterate through items retrieved from Firebase using ngrepeat. Although I can see the items in the console, the expressions are not working as expected. I have tried various solutions, but nothing seems to be working. Any assist ...

Using Vuex as a global event bus ensures that all subscribers will always receive notifications for

For a while now, I have relied on a global event bus in Vue - creating it as const bus = new Vue(). It works well, but managing subscriptions can get tedious at times. Imagine subscribing to an event in a component: mounted() { bus.$on('some.event ...

Utilizing AngularJS to show content based on regular expressions using ng-show

With two images available, I need to display one image at a time based on an input regex pattern. Here is the code snippet: <input type="password" ng-model="password" placeholder="Enter Password"/> <img src="../close.png" ng-show="password != [ ...

Incorporate a unique identifier for dynamic elements

Is there a way to give generated divs the same name so I can markup them easily? $.getJSON("data/reviews.json", function(data){ for(var i=0; i<data.length; i++) { var review = sym.createChildSymbol("Template", "content"); review.$("title").html ...

Discovering details regarding cookies established by an external domain

Is it possible to retrieve the host address of the domain that created a cookie on my webpage? Here is the scenario I am facing: I am on "domain A" and have a script linked from "domain B". A method on "domain B" sets a cookie on my "domain A". How can ...