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 the adjustments made to your code.

I have made several changes:

  1. switched to script setup for enhanced readability
  2. replaced computed with reactive using the $red syntax from reactivity transform
  3. Instead of adding items with name: 'three' and displaying item.display, I now add with display: 'three'.

The code now functions properly, and I believe the switch from computed to reactive is the key. I will further research this to provide an updated response.

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

Why do static files in Node.js + Express.js on Windows take up to two minutes to load?

I'm encountering an issue in my Windows environment with Node.Js/Express.js where static JS files can sometimes be labeled as 'pending' in the browser (even with caching disabled) for up to two minutes before finally downloading successfully ...

What steps should be taken to effectively integrate Amplify Authenticator, Vue2, and Vite?

Embarked on a fresh Vue2 project with Vite as the build tool. My aim is to enforce user login through Cognito using Amplify. However, when I execute npm run dev, I encounter the following issue: VITE v3.1.3 ready in 405 ms ➜ Local: http://127.0.0 ...

The 'split' property is not present on the 'string | number | {}' type

Just starting out with Typescript and I've encountered an error stating that the split method does not exist on type number. I've tried narrowing down the type by checking the value's type, but so far it hasn't been successful. Below is ...

Are there any methods to implement object-oriented programming in JavaScript?

The concept of prototype-based object-oriented programming in JavaScript is intriguing, but there are many scenarios where the need for class-based object creation arises. Consider a vector drawing application, where the workspace begins empty and you can ...

Is it possible to delete a <div> tag based on screen size using jQuery or JavaScript?

Hello, I was curious if it's possible to dynamically remove a specific div tag using jQuery or JavaScript when the screen reaches a certain size, for example 500px. ...

Navigating to the next page in Angular JS by clicking "Save and Next" which

Hey there, I'm currently diving into Angular JS and working on a CMS system with it. Right now, we're dealing with around 30 to 40 Objects that we load into a list. Whenever one of these objects is clicked, a Modal Window pops up using the Modal ...

What is the method for programming a Discord bot to respond to your messages?

As a beginner in coding, I have been working on creating a bot that can respond with whatever is said after the command !say. For example - if you type !say hello, the bot will reply with "hello". This is what I have attempted: let args = message.content ...

Combining react-draggable and material-ui animations through react-transition group: a comprehensive guide

Trying to incorporate react-draggable with material-UI animations. One approach is as follows: <Draggable> <Grow in={checked}> <Card className={clsx(classes.abs, classes.paper)}> <svg classN ...

Encountering issues with implementing casl for role-based permission management

Here is my definition of ability using the casl library: ability.js import { AbilityBuilder } from '@casl/ability'; export default function() { AbilityBuilder.define(can => { switch(localStorage.getItem('role')) { case & ...

Experiencing Difficulty accessing Methods with Jmeter-Webdriver

var pkg = JavaImporter(org.openqa.selenium) var support_ui = JavaImporter(org.openqa.selenium.support.ui.WebDriverWait) var wait = new support_ui.WebDriverWait(WDS.browser, 5000) **var support_page=JavaImporter(org.openqa.selenium.WebDriver.Timeouts)** **v ...

Saving a collection of unique identifiers in Firebase

Struggling to find a solution for organizing Firebase data: Each user has posts with generated IDs, but how do I store these IDs in a user node efficiently? Currently using string concatenation and treating them like a CSV file in my JS app, but that feel ...

Updating reference value with a watcher using Vue 3 and the Composition API

I'm having trouble updating a ref within a watch function. My goal is to monitor changes in the length of an array, and if the new length is less than the old one, I want to update a specific ref called selectedTitle. setup() { const slots = useS ...

The PHP plugin I created seems to be adding an unnecessary whitespace at the end of its output

New to the world of PHP, I challenged myself to create a simple PHP plugin that generates a text greeting based on the time of day for users. To use my plugin, simply drop the .php file into your 'includes' folder and insert a line where you want ...

Guide on verifying Unicode input in JavaScript?

I am looking to create a form where the user can only input Khmer characters (Unicode characters) and display an alert if they input anything else. Khmer Name: <input type="text" class="namekh" name="namekh"> To achieve this, here is a snippet of m ...

Calling Ajax inside each iteration loop

I have encountered numerous posts discussing this topic, but the solutions I came across do not quite suit my needs. Some experts suggest changing the code structure, however, I am unsure of how to go about doing that. What I desire: 1) Retrieve a list ...

Warning: Next.js is throwing a hydration error because the server HTML does not include a matching <main> element within a <div>

I have been encountering hydration issues in my next.js application. After extensive troubleshooting, I have found that the culprit might be the higher order component called withAuth.js The error message displayed is: Warning: Expected server HTML to con ...

Utilizing navigation buttons to move between tabs - material-ui (version 0.18.7)

I'm currently using material ui tabs and attempting to incorporate back and next buttons for tab navigation. However, I've run into an issue - when I click the back or next buttons, the tabs do not switch. Here is my existing code snippet: ... ...

JavaScript conversion of arrays to JSON data structures

Here is the code snippet along with the variable 'polygon': var directionsDisplay; var directionsService = new google.maps.DirectionsService(); var map; var bermudaTriangle; var directionsPoints; var example; var rez; function initialize() { ...

Zingchart encounters issues when attempting to plot a CSV file containing over 10 columns

Situation: I want to create a Zingchart graph from a CSV file containing 37 columns. The header in the CSV file will be used as the legend for the graph. Issue: When I define less than 10 elements in the header (including the X-axis name), everything wo ...

Implementing Partial Login and Registration Views using AngularJS in conjunction with MVC5 and ASP.NET Identity

Embarking on the journey of creating a Single Page Application with log-in/register functionality using MVC5, ASP.NET Identity, and Angular feels like diving into a vast ocean of web development technologies. Despite being new to this realm, I delved into ...