Exploring the reactivity of Vue on object graphs that include cyclic references

First and foremost, let me provide some context:

I am currently in the process of developing a minesweeper application using VueJS. The grid is structured as an object tree, where each Box object contains a 'neighbors' property that stores Box objects adjacent to it.

The structure is circular, but everything seems to be functioning correctly.

However, I encountered a problem when attempting to generate a larger grid (50x50) after having successfully tested with a smaller grid (5x5). VueJS threw a 'Maximum call stack size exceeded' error while setting up observers. Here is a link to the log for reference: https://i.stack.imgur.com/kMfyt.png

It appears that the tree is too large for Vue to manage reactivity effectively.

This assumption was validated when I attempted freezing my object which resolved the callstack error:

data() {
    const game = new MinesWheeper(30, 30, 40)
    Object.freeze(game)
    return {
      game
    }
  }

Although reactivity is compromised due to the freeze, it is essential for unveiling boxes upon clicking.

Here are my questions/considerations:

  • Have you encountered this callstack issue with Vue before or have any insight?

  • Is there a way to implement reactivity in VueJS with such a complex object structure? Would VueX be a viable solution? I'm unfamiliar with it.

  • Alternatively, should I explore other frameworks aside from VueJS? Perhaps opt for Vanilla JS instead?

Thanks in advance for your assistance. Apologies if my post appears disorganized - it's my first one in five years and I must admit, I'm feeling a bit nervous!

Edit: Below is a representation of the game object:

game: { // MinesWheeper
  _grid: { // Grid
    "_bombsNumber": 40
    _boxes: [ // Array of Box
      {
        "_hasBomb": true
        "_index": 0
        "_isRevealed": false
        "_neighbors": [Box, Box, Box, Box]
        "hasBomb": true
        "index": 0
        "isRevealed": false
        "nearBombs": 1
      }
    ]
  }
}

Moreover, when clicking on a Box, the following actions need to take place: - End the game if Box.hasBomb is true - Display the content if no bomb is present - Or recursively trigger the reveal() method if Box.nearBombs equals 0

My Box.reveal() method utilizes recursion:

reveal() {
    if (this._isRevealed) return

    this._isRevealed = true

    if (this.hasBomb) {
      console.log('Game over')
    } else if (this.nearBombs === 0) {
      this._neighbors.forEach(neighbor => {
        neighbor.reveal()
      })
    }
  }

Hence, I believe reactivity is crucial for updating the view with each Box.reveal() invocation.

Answer №1

Within your one-dimensional array of Box objects, each object is equipped with a `neighbors` array that holds references to its neighboring Boxes in the main array.

To prevent Vue from setting up reactivity on the `neighbors` property, you can utilize Object.defineProperty() and specify `configurable: false`. The remaining properties of the Box object will still remain reactive.

I've prepared a demonstration for you which you can explore. Most of the crucial information can be found within the components/HelloWorld.vue component.

The example might seem complex as using an array would typically enable easy referencing of "prev" and "next" elements through index. However, I opted to introduce cyclic references between objects for this scenario.

In my testing environment, utilizing 10000 items in the array resulted in an error. Alternatively, replacing the call to this.setupRelatioships(boxes); with

this.setupRelatioshipsWithProperties(boxes);
resolved the issue.

setupRelatioships(boxes) {
      for (let i = 0; i < boxes.length; i++) {
        boxes[i].prev = i > 0 ? boxes[i - 1] : null;
        boxes[i].next = i < boxes.length - 1 ? boxes[i + 1] : null;
      }
    },
    setupRelatioshipsWithProperties(boxes) {
      const opts = {
        configurable: false,
        enumerable: true,
        writable: true
      };

      for (let i = 0; i < boxes.length; i++) {
        Object.defineProperty(boxes[i], "prev", {
          ...opts,
          value: i > 0 ? boxes[i - 1] : null
        });
        Object.defineProperty(boxes[i], "next", {
          ...opts,
          value: i < boxes.length - 1 ? boxes[i + 1] : null
        });
      }
    },

You may also refer back to the earlier mentioned Demo (credits to Guillaume Chau)

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

What causes elements to transition in from the top-left corner when using TransitionGroup in Vue 3?

I'm currently working on adding animations to a small component that consists of a title, an image, and a set of buttons. The animation rules are as follows: Initially, only the title and the image are visible, On hover, the title disappears upwards ...

The v-app-bar extends down to fit the shorter page content rather than staying at its usual height

I am looking to enhance my application with a new header section, and I have referred to the recommended application markup while doing so. This is the code snippet that I used: <v-app> <v-app-bar> header </v-app-bar> <v-main ...

Which type of comparator does Vuetify utilize for sorting string columns in a data table?

If I modify the standard Vuetify example to have Calorie values as Strings instead of numbers, and now the data is not entirely numeric: https://codepen.io/hobbeschild/pen/WNQWmrJ What sorting method does Vuetify use internally for ordering these Strings? ...

The custom element is unfamiliar: Have you properly registered the component? Vue.js in Laravel

I encountered this problem: Unknown custom element - did you register the component correctly? I am trying to implement a like system using Vue.js. Can someone please guide me on how to fix this issue? Despite my best efforts, it seems like everything is i ...

Is there a way to access a computed property within methods?

Currently, I am utilizing this particular package to implement infinite scrolling in Vue. In order to continuously add new elements during each scroll, I fetch JSON data from my API server and store it in a data object. Subsequently, I divide the array in ...

Exploring Vue and Nuxt JS: What Causes the Issue of Unable to Create the Property 'display' on the String 'bottom:30px;right:30px;'

this piece of code is designed for a component that allows users to jump back to the top of the page. However, after refreshing the page, it stops working and throws an error. The project uses the Nuxt and Vue framework. Can anyone identify the reason behi ...

Error message: Unable to load image from local source in Vue application

I am currently working on creating a simple dice roll game in VueJs. However, I encountered an issue with using the dice images as it keeps giving me a 404 error. When I attempted to use require(), it stated that the function was not defined. After some t ...

I'm having trouble launching the Vue UI after successfully installing the Vue CLI - any ideas why?

After installing the following versions: node 8.11.2 npm 6.3.0 I proceeded to install the Vue CLI (@vue/[email protected]): npm i -g @vue/cli However, upon running the command below: vue ui An error message was displayed as follows: Error: Cann ...

Discover the power of Vuetify's message translation in combination with Nuxt i18n

I am looking to implement translated error messages for Vuetify validation failures Template section <v-text-field v-model="message.name" outlined :label="$t('page.contact.form.name')" :rules=nameValidation ...

Error Encountered: unable to perform function on empty array

I've encountered an issue with my Vue JS 2.6.10 application after updating all packages via npm. Strangely, the app works perfectly fine in development environment but fails to function in production. The error message displayed is: Uncaught TypeErr ...

The Swiper library is incompatible with the "vue" version "^2.6.11" and cannot be used together

I've been attempting to incorporate Swiper with "vue": "^2.6.11", however I keep encountering runtime errors. Despite following the instructions on , and modifying the imports as advised: // Import Swiper Vue.js components import { ...

Out of Sync Promise Chain

I recently encountered an issue with promise chaining in JavaScript, specifically while working with Vue.js. Here is my code: I have an addItem function that inserts an item into the database. My goal is for this function to first insert the data into the ...

Struggling to integrate font-awesome into my Vue.js project

Observation: The stars should be displayed using the Font Awesome font. Issue: An error font is being displayed instead of Font Awesome, indicating that it is not being loaded properly. Hello. I am encountering problems with including Font Awesome in my p ...

Utilizing the power of Vue Router with DataTables

I am currently facing an issue where I want to include links or buttons in my DataTable rows that can navigate to a vue route when clicked. The straightforward approach would be to add a normal <a> element with the href attribute set to "/item/$ ...

The Vuex state is being modified by an action, yet the associated computed property fails to react accordingly

I am facing an issue with fetching data from firebase using a store.js file that I have imported into the main.js. The problem is that the data does not display correctly when the page loads. However, when I check the console.log in mutations, it shows t ...

Tips for overcoming indentation issues using ESLint?

Ever since I started using Vue 3 with ESlint, I've been encountering a new problem. Whenever I try to run my code, I am bombarded with numerous errors like: --fix option.

I'm looking for ways to disable this troublesome feature of ESlint. H ...

Is it possible for VueX's state management store to emit events to components as well?

My component tree looks like this: App List ItemDetails Widget1 ... The List component contains a filters form that is applied when the button is pressed. Widget1 has a button that applies another filter (by id) to the list. Applying this f ...

The Selenium test functions correctly in the production environment, however it encounters issues when run

I am facing an issue with my Vue.js application while writing Selenium tests. The test passes when run against the deployed production version of the app, but fails when run against a local version. When running the app locally, it is not from a build, bu ...

Tests using Cypress for end-to-end testing are failing to execute in continuous integration mode on gitlab.com

Challenges with Setting Up Cypress in Gitlab CI We have been facing difficulties setting up Cypress in the CI runners of gitlab.com using the default blueprint from vue-cli to scaffold the project. Despite trying various configurations in the gitlab.yml f ...

Retrieving information from a Vue component using AJAX

One of the components in my Vue project is named "testrow". It contains a form with two selections: "description" and "parameter". I need to make sure that the "parameter" changes based on the selected 'description' value stored in my database. T ...