Manipulating deeply nested state data in Vuex actions can be a challenge

When working in the store, I have an action that updates certain data. The action is structured like this:


setRoomImage({ state }, { room, index, subIndex, image }) {
      state.fullReport.rooms[room].items[index].items[subIndex].image = image;
      console.log(state.fullReport.rooms[room].items[index].items[subIndex])
    },

Since the data is dynamic, I cannot hard code the properties and need to dynamically change nested values. Here's how the data appears:

fullreport: {
    rooms: {
        abc: {
          items: [
            {
              type: "image-only",
              items: [
                {
                  label: "Main Image 1",
                  image: ""
                },
                {
                  label: "Main Image 2",
                  image: ""
                }
              ]
            }
          ]
        }
      }
}

After dispatching the action, although I see successful mutation of the sub-property image in the console, the value doesn't change when accessing the VueX store from the Vue DevTools within Chrome. This is what the console displays:

https://i.stack.imgur.com/z2Smr.png

I'm wondering why this is happening. Even though the data is visibly changing, the state isn't reflecting it, leading to no rerendering of components.

I attempted using Vue.set instead of a simple assignment, but had no success :(

Vue.set(
  state.fullReport.rooms[room].items[index].items[subIndex],
  "image",
   image
 );

Edit:

In response to suggestions by David Gard, I tried the following:

Additionally, I rely on Lodash _ (I understand duplicating entire objects isn't ideal), here is the updated mutation code block.

let fullReportCopy = _.cloneDeep(state.fullReport);
fullReportCopy.rooms[room].items[index].items[subIndex].image = image;
Vue.set(state, "fullReport", fullReportCopy);

Within the computed property where state.fullReport serves as a dependency, I added a console.log which outputs a string each time the computed property is recomputed.

Upon committing this mutation, I observe the computed property logging the string every time, but the received state remains unchanged. It seems that Vue.set merely indicates to the computed property that the state has been altered without actually implementing the change. Consequently, there are no visible alterations in my component's user interface.

Answer №1

Dealing with complex nested state in Vuex

Managing deeply nested state in Vuex can quickly become a challenge, especially when it comes to handling Arrays and Objects differently. One limitation of Vuex is the lack of support for reactive Maps.

In my experience, I've encountered projects that require dynamic property setting with multiple levels of nesting. One approach that has worked for me is recursively setting each property.

Here's a functional but not elegant solution:

function createReactiveNestedObject(rootProp, object) {
  let root = rootProp;
  const isArray = root instanceof Array;

  Object.keys(object).forEach((key, i) => {
    if (object[key] instanceof Array) {
      createReactiveArray(isArray, root, key, object[key])
    } else if (object[key] instanceof Object) {
      createReactiveObject(isArray, root, key, object[key]);
    } else {
      setValue(isArray, root, key, object[key])
    }
  })
}

function createReactiveArray(isArray, root, key, values) {
  if (isArray) {
    root.push([]);
  } else {
    Vue.set(root, key, []);
  }
  fillArray(root[key], values)
}

function fillArray(rootArray, arrayElements) {
  arrayElements.forEach((element, i) => {
    if (element instanceof Array) {
      rootArray.push([])
    } else if (element instanceof Object) {
      rootArray.push({});
    } else {
      rootArray.push(element);
    }
    createReactiveNestedFilterObject(rootArray[i], element);
  })
}

function createReactiveObject(isArray, obj, key, values) {
  if (isArray) {
    obj.push({});
  } else {
    Vue.set(obj, key, {});
  }
  createReactiveNestedFilterObject(obj[key], values);
}

function setValue(isArray, obj, key, value) {
  if (isArray) {
    obj.push(value);
  } else {
    Vue.set(obj, key, value);
  }
}

If you have a more efficient way to handle this scenario, I'd love to hear about it!

Edit:

Here's how I implement the above solution in my Vuex store:

// in store/actions.js

export const actions = {
  ...
  async prepareReactiveObject({ commit }, rawObject) {
    commit('CREATE_REACTIVE_OBJECT', rawObject);
  },
  ...
}

// in store/mutations.js
import { helper } from './helpers';

export const mutations = {
  ...
  CREATE_REACTIVE_OBJECT(state, rawObject) {
    helper.createReactiveNestedObject(state.rootProperty, rawObject);
  },
  ...
}

// in store/helper.js

// include the functions mentioned above

export const helper = {
  createReactiveNestedObject
}

Answer №2

Leaving out the recommended practices from the comments.

All you have to do is instruct Vue when the object changes (Complex objects are not reactive). Utilize Vue.set. You must set the entire object:

   Vue.set(
     state,
     "fullReport",
     state.fullReport
   );

For more information, visit the documentation: https://v2.vuejs.org/v2/api/#Vue-set

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

Step-by-step guide on populating input fields with JSON data for each row

As a beginner in Programming, I have been searching for an answer to the following problem on this site for days but haven't been able to figure it out yet. To explain briefly, there will be an input form where the user can define the number of rows ...

Rotate a sphere in three.js in order to align the mapped image of the globe with the GeoJSON data that is displayed in the same three

I have successfully implemented a globe in three.js with an image mapped onto the sphere. Furthermore, I am utilizing the ThreeGeoJSON library to visualize geojson data on top of the globe. However, the geographies from the geojson data do not align corr ...

Whenever I try to retrieve data from MongoDB using Node.js, I consistently encounter a timeout error

Currently, I am in the process of developing a website using React.js for the front end, Node.js for the back end, and MongoDB as the database. Dummy data has been inserted into the database which can be viewed . The database has been created in Atlas, the ...

Center the image within the div by setting its position to absolute

<div class='img-box'> <img /> //position absolute <img /> //position absolute <img /> //position absolute <img /> //position absolute I am struggling to center the images within this div because of their absolute p ...

Struggling to figure out how to make vue-router function properly

Successfully implemented this on a single page. The problem arises when I integrate vue-router as nothing appears to be rendering. In my main.js, if I provide sample text and make some adjustments, it displays without any issues. It seems like the problem ...

User form not triggering post requests

I have a unique react blog application embedded with a form for submitting intriguing blog posts. The setup includes a server, routes, model, and controllers for fetch requests. Surprisingly, everything functions impeccably when tested on Postman. However, ...

Troubleshooting: Problems with AngularJS $http.get functionality not functioning as expected

I have a user list that I need to display. Each user has unread messages and has not created a meal list yet. I want to make two http.get requests within the main http.get request to retrieve the necessary information, but I am facing an issue with asynchr ...

Steps for aligning an image to the right using Bootstrap Vue

I'm currently learning Bootstrap Vue and I'm trying to align an image on the right using a b-card header. The challenge I'm facing is that when I add the image, it disrupts the perfect size of the template header, causing the image to show a ...

Experiencing difficulties with mocha and expect while using Node.js for error handling

I'm in the process of developing a straightforward login module for Node. I've decided to take a Test-Driven Development (TDD) approach, but since I'm new to it, any suggestions or recommended resources would be greatly appreciated. My issu ...

Issue with making a call to retrieve an image from a different directory in ReactJS

This is how it appears <img className='imgclass' src={"../" + require(aLc.value)} alt='' key={aLc.value} /> I am trying to create a path like ../m/b/image.jpg, where aLc.value contains the path /m/b/image.jpg. I need to add only ...

Struggling to retrieve data from Firebase in React Native?

It's been a challenge for me as a newcomer to React Native trying to retrieve data from a Firebase database. This is the process flow of how my data is handled: 1. A user selects locations and trip details (name, startDate, endDate) --> stored in ...

Unusual actions when making a $.ajax call using the PUT method

When making a call to $.ajax, I use the following code: $.ajax({ type: 'PUT', url: model.url(), data: {task: {assigned_to: selected()}}, contentType: 'application/json' }) The function selected() returns an array. However, th ...

Is it possible to have both Node.js and browser code in the same file using webpack, while ensuring that only the browser code is accessible and the Node.js code remains hidden?

I need to work with a file that contains both Node.js and browser code. It's crucial that the Node.js code remains hidden when running in the browser environment. Is it possible for Webpack to exclude specific sections of the code based on the enviro ...

Check domains using Jquery, AJAX, and PHP

I'm currently developing a tool to check domain availability. Here is the PHP code I have so far: <?php $domain = $_GET["domname"]; function get_data($url) { $ch = curl_init(); $timeout = 5; curl_setopt($ch, CURLOPT_URL, $url); ...

Accessing the first child node in JsTree

Is it possible to display only the first child of a list using the JStree plugin to create a tree? For example, if I have a list with 5 children, some of which have nested children, I am looking for a way to display only the first child of each <li> ...

observing the value of the parent controller from the UI router state's resolve function

I am facing an issue in my AngularJS application while using ui-router. There are three states set up - the parent state controller resolves a promise upon a successful request, and then executes the necessary code. In the child state portfolio.modal.pate ...

Automatically resizing font to fit the space available

I am attempting to achieve the task described in the title. I have learned that font-size can be set as a percentage. Naturally, I assumed that setting font-size: 100%; would work, but unfortunately it did not. For reference, here is an example: http://js ...

Error: The callback specified is not a valid function

Here is a function example: reportAdminActions.reportMemberList(project, function(data) { console.log(data); }); This particular function gets called by another ajax operation, similar to the one shown below: reportMemberList: function(projectId, ca ...

Translating SQL to Sequelize Syntax

I have an SQL query that I need to rewrite as a sequelize.js query in node.js. SELECT historyTable1.* FROM table1 historyTable1 WHERE NOT EXISTS ( SELECT * FROM table1 historyTable2 WHERE historyTable2.id=historyTable1.id AND historyTable2.da ...

Concentrating on a Div Element in React

I'm trying to set up an onKeyPress event that will be triggered when a key is pressed while a 'Level' element is displayed. I know that the div needs to be focused for events to register, but I'm not sure how to do this with functional ...