How to copy data from an array data object to a new one and add values to it? (Vue2)

Hi,

I retrieve data from an API that I put in an array called userData in the data object in the component.

export default {
  name: "UserView",
  components: { TheHeader },
  props: {
    msg: String,
  },
  data() {
    return {
      userData: [],
      favorite: false,
    };
  },

I then want to extract parts of the data from each individual object in the array and manipulate it as well as add new properties to the array.
the userData array with objects from api, looks like this:

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
    },
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
    }
]``` 

So I want to create a new array where I pick these values and add a boolean, developer that is set to true
I then want to be able to run methods on certain values, for example, toLowerCase ()
, etc.
new array :

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "developer": "true",
    "email": "Sincere@april.biz",
    "address": {
      "city": "Gwenborough",
    },
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "developer": "true",
    "email": "Shanna@melissa.tv",
    "address": {
      "city": "Wisokyburgh",
    }
]

I wonder what’s the best way to do this?

I want the new array with objects to be in the data object, Or in another way so it is reactively available?
The array I also want to add as a prop.

So I end up with something like:

export default {
  name: "UserView",
  components: { TheHeader },

  props: {
    msg: String,
    newArray: Array,
  },

  data() {
    return {
      userData: [],
      newArray: [],
      favorite: false,
    };
  },
  methods: {
    changeFavorite(arg) {
      this.favorite = arg;

    },
  }, 

I would use a computed property dort that. For computing the property, array.map is probably what you will want to use.

1 Like

Thanks, @fiedsch for your advice.

I´m trying so solve this. I have fixed what the function should do:
This is working when tried with dummydata in a javascriptfile, it´s takes out first name, do loLowerCase() etc.

let newArr = array2.map((array2) => ({
  id: array2.id,
  name: array2.name.split(" ")[0],
  email: array2.email.toLowerCase(),
  city: array2.address.city,
  developer: true,
}));  

But when I to implement this in Vue I get stuck.

  computed: {
    manipulatedUserData() {
      let newArr = [];
      newArr = this.userData.map((user) => ({
        id: user.id,
        name: user.name.split(" ")[0],
        email: user.email.toLowerCase(),
        city: user.address.city,
        developer: true,
      }));
      return newArr;
    },
  },

the userData comes from the data() and is collected in an array of objects via a creadet() API call.
When I run it I try reach the newArray in template

 <article
            class="cardStyle userCard"
            v-for="user in newArr"
            :key="user.id"
          >
            <div class="textInCard">
              <div class="h4StarWrapper">
                <router-link v-bind:to="'/user/' + user.id">
                  <h4>{{ user.name }}</h4> etc...........

In the console I get this error message…

[Vue warn]: Property or method "newArr" is not defined on the instance but referenced 
during render. 
Make sure that this property is reactive, either in the data option, 
or for class-based components, by initializing the property. 
See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.

found in

---> <UserView> at src/components/UserView.vue
       <App> at src/App.vue
         <Root>

and if look more in the error message you see the rendered line that breaks it

              _vm._l(_vm.newArr, function(user) {
``` 

Do have thoughts on how I can fix this?
I tried wrap it in mounted(), if it had something to to with the lifecycle hook, I´m if the data from userData is not there at the beginning? it loads in a created() API call. But have tried wrap the computed method in a mounted() but that dot not work.

            return _c(
              "article",
              { key: user.id, staticClass: "cardStyle userCard" },
              [
                _c("div", { staticClass: "textInCard"

could be shorter:

computed: {
    manipulatedUserData() {
    return this.userData.map((user) => ({
        id: user.id,
        name: user.name.split(" ")[0],
        email: user.email.toLowerCase(),
        city: user.address.city,
        developer: true,
      }));
    },
  },

but that is only a matter of taste. I think your error is here:

which should be

<article
            class="cardStyle userCard"
            v-for="user in manipulatedUserData"
            :key="user.id"
          >

as newArr is not the name of the computed property (it is only al local variable in the manipulatedUserData() function).

Edit: the newArray: [], in data() is not used anywhere and should be removed.

1 Like

thank you @fiedsch
works perfectly.

what is the best approach to change the “favorites: false,” property. when you toggle a button in computed array?

I guess I can not call method on say button to change data in computed arrays? :thinking:
I have tried to set the value like:

                <a
                  class="starLink"
                  v-on:click="user.favorites = true"
                >
                  <div class="starWrapper">
                    <i class="starNoFill starsize far fa-star"></i>
                  </div>
                </a>
```
one other thing I have tried is to go via the methods
````template
                <a
                  class="starLink"
                  v-on:click="changeFavorite(false)" >
                  <div class="starWrapper">
                    <i class="starFill starsize fas fa-star"></i>
                  </div>
                </a>

And where the method works like this:

  methods: {
    changeFavorite(arg) {
      this.favorites = arg;
    },
  },

Well I understand that this points to the data() where the favorites don’t lives hence that property is created inside the computed method,

I don´t understand how I can access this “favorites: false,” prop and google it with a button or link.

In the end what I want to create is that when click on a star the value change to true and I need store value also as session or local storage. But start with I must fix so I can google it, and later fix the session or local storage.
image

You could augment the original data with initial values favourite: false and then call some method in your onClick that changes the data that eventually will end up in the computed property.

The problem I see is that you would have to persist that information to the server. Otherwise you would lose the user‘s favorites once the data gets loaded from the server again.

Edit:
Upon reading your post again: I do not quite get the usage/meaning of favorite in your data(). Does that relate to the entries in userData? If so, shouldn’t that be an array also?

1 Like

you right, I think I put the favorite boolean outside the userData array, first just to do some testing.

But it it is key value pair of the userData. The function of the favorites boolean is to toogle a list. To keep the persist I use local storage.

I have solve almost everything now:
First at created() I construct the userData and manipulate the data, I also create a localstorage array of objects that sets the value of favorite to false.

 data() {
    return {
      /* Array with objects of userdata for each user*/
      userData: [],
    };
  },

  async created() {
    try {
      // this is pointing to -> data() -> return
      /* hämtar data och ordnar den efter behov, lägger
      ky value pairs etc */
      let constructArr = [];
      constructArr = await CallApi.getPosts(url);
      this.userData = constructArr.map((user) => ({
        id: user.id,
        firstName: user.name.split(" ")[0],
        fullName: user.name,
        email: user.email.toLowerCase(),
        companyName: user.company.name,
        street: user.address.street,
        zip: user.address.street,
        city: user.address.city,
        favorites: false,
      }));
      // Create local storage
      // create a new array, destruction of the constructArr, only grabs the id
      let localArray = constructArr.map(({ id }) => ({
        id,
        favorites: false,
      }));
      // create a string of the localarray and sets the local storage
      // check if localstorage with key "PhotoData" exist, if not create new
      if (!localStorage.getItem("PhotoData")) {
        localStorage.setItem("PhotoData", JSON.stringify(localArray));
      }
      //localStorage.setItem("PhotoData", JSON.stringify(localArray));
    } catch (err) {
      this.error = err.message;
    }
  },

Then I have a method that starts when user press a button which update the userData in data() object.

changeFavorite(id, arg) {
      let idIndex = id - 1;
      this.$set(
        this.userData[idIndex],
        (this.userData[idIndex].favorites = arg)
      );

It also updates the localstorage array. It it works fine.
the toogling is handled with computed functions,

  filteredFavoriteUserData() {
      let tempArr = this.userData;

      tempArr = tempArr.filter((user) => {
        return user.favorites;
      });
      return tempArr;
    },
    // Not favorite listed users, "default" list.
    filteredUserData() {
      let tempArr = this.userData;

      tempArr = tempArr.filter((user) => {
        return !user.favorites;
      });
      return tempArr;
    },

So whats left to fix and what I have been struggling with tonight is how to update the userData array favorite value, from the localstorage.

well I have tried a lot of things, but just can´t get it to work. First of all the data from the localstorage is ok and handled.

 async mounted() {
    // LISTEN AFTER ALL LOCALSTORAGE
    //localStorage.setItem(id, arg);
    /*this.userData.map(() => ({
      favorites: null,
    }));*/
    console.log(this.userData);
    // Local storage handling, collects the stored data
    let arrayOfLocal = localStorage.getItem("PhotoData", arrayOfLocal);
    // parse data to become a real array
    arrayOfLocal = JSON.parse(arrayOfLocal);
    //this.userData = arrayOfLocal.map((user) => ({}));
    // Insert updated favorites in the array of obj

    arrayOfLocal.forEach((element) => {
      console.log("hello from mounted");
      console.log(element);
      //let idIndex = element.id - 1;
      console.log(this.userData);
      /*this.userData[idIndex][8] = element.favorites;
      console.log(this.userData[idIndex][8]);*/
    });
  },

The console logs of "hello from mounted and the following two, prints out this:
image
element in the foreach is ok, then. it comes from the localstorage. and that values I want to update the userData with…

When I update the userDate with just on value, in the methods function changeFavorite, I use the $set, and it works fine, put have not manged to pull something with the $set of in the foreach array.

 changeFavorite(id, arg) {
      let idIndex = id - 1;
      this.$set(
        this.userData[idIndex],
        (this.userData[idIndex].favorites = arg)
      );

The syntax for $set is Vue.set(object, propertyName, value) so I think it should be

this.$set(
        this.userData[idIndex],
        'favorites',
        arg
);

Apart from this: you have numerical array indexes 0, 1, … in your local storage and your userData and an object property id that have to match somehow. Can you make sure, that that always holds true or might there be a situation when a call to your backend yields different data for the same index position that would then no longer correspond to the index in your local storage?

Did you try to call your changeFavorite in the arrayOfLocal.forEach(...)?

1 Like

Thank you, the app is now almost ready.
I triggered to changeFavorite method and it worked fine.
I also had to set this process in the created() and not in mounted as I first tried. I guess mounted() is rendered before the async API call in the created() is done.

So the created() now looks like this:

  async created() {
    try {
      // this is pointing to -> data() -> return
      /* hämtar data och ordnar den efter behov, lägger
      ky value pairs etc */
      let constructArr = [];
      constructArr = await CallApi.getPosts(url);
      this.userData = constructArr.map((user) => ({
        id: user.id,
        firstName: user.name.split(" ")[0],
        fullName: user.name,
        email: user.email.toLowerCase(),
        companyName: user.company.name,
        street: user.address.street,
        zip: user.address.street,
        city: user.address.city,
        //favorites: false,
      }));

      //  HANDLE LOCAL STORAGE:
      // create a new array, destruction of the constructArr, only grabs the id
      let localArray = constructArr.map(({ id }) => ({
        id,
        favorites: false,
      }));
      // create a string of the localarray and sets the local storage
      // check if localstorage with key "PhotoData" exist, if not create new
      if (!localStorage.getItem("PhotoData")) {
        localStorage.setItem("PhotoData", JSON.stringify(localArray));
      } // IF localstorage exists, 1 collect data and transform from string to array
      else if (localStorage.getItem("PhotoData")) {
        let arrayOfLocal = localStorage.getItem("PhotoData", arrayOfLocal);
        // parse data to become a real array
        arrayOfLocal = JSON.parse(arrayOfLocal);

        // Insert updated favorites in data() -> userData array.

        arrayOfLocal.forEach((element) => {
          this.changeFavorite2(element.id, element.favorites);
        });
      }
    } catch (err) {
      this.error = err.message;
    }
  },

and the rest like this

  computed: {
    // Returns favorite listed users.
    filteredFavoriteUserData() {
      let tempArr = this.userData;

      tempArr = tempArr.filter((user) => {
        return user.favorites;
      });
      return tempArr;
    },
    // Not favorite listed users, "default" list.
    filteredUserData() {
      let tempArr = this.userData;

      tempArr = tempArr.filter((user) => {
        return !user.favorites;
      });
      return tempArr;
    },
  },

  methods: {
    // Vue set, takes 3 arg,
    // 1 refersns till objektet vi vill ändra, kan ej vara rootobjekt av en vueinstans, eller en ny vueinstans
    // andra är indexplatsen
    // 3 är värdet vi vill ändra

    /* arg = true/false, id - 1, to fit index,
    sets new value in the data() object array of nested objects
    favorite listed users, toogles via the star icon */

    changeFavorite(id, arg) {
      this.changeFavorite2(id, arg);
      //KEY: user id and VALUE:arg = true or false saved in localstorage
      //localStorage.setItem(id, arg);

      // Local storage handling, collects the stored data
      let arrayOfLocal = localStorage.getItem("PhotoData", arrayOfLocal);
      // parse data to become a real array
      arrayOfLocal = JSON.parse(arrayOfLocal);

      // Insert updated favorites in the array of obj
      arrayOfLocal.forEach((element) => {
        if (element.id === id) {
          element.favorites = arg;
        }
      });

      // let arrayIn = JSON.stringify(arrayOfLocal);
      //  push / write over local storage
      // create a string of the localarray and sets the local storage
      localStorage.setItem("PhotoData", JSON.stringify(arrayOfLocal));
    },

    changeFavorite2(id, arg) {
      let idIndex = id - 1;
      // console.log(this.userData);
      this.$set(
        this.userData[idIndex],
        (this.userData[idIndex].favorites = arg)
      );
      this.filteredFavoriteUserData;
      this.filteredUserDat;
    },
  },
};

But the strange thing is that sometimes when click on change favorite it works, I mean it works it changes localstorage settings and all but the change in the userData() on some clicks works and on others times it don’t.

I made a small youtube film to explain.

I checked you video, but it i hard to follow if you only see fragments oft the code.

My first guess (a already mentioned) is that you mixup the id attribute in your this.userData's entries with the index position in your arrays. Once you remove an element from your arrays, id: 1 won’t necessarily be at index 0 (which try to map with let idIndex = id - 1).

I would go this path:

  • when a user clicks an entry (favourite) ad the respective id to your local storage
  • access the items in your arrays with array.find rather than relying on the index position
1 Like

hmm , well I that may be the case. But I always have the same numbers of entry’s added in the localstorage I´m I write over the whole thing, no update. That’s way I thought the index would always match. Also there is never any change in the API who collects the userdata. Well that’s so good to depend on, but anyway that’s how it is set up right now.

array.find(), you means in the computed()


  computed: {
    // Returns favorite listed users.
    filteredFavoriteUserData() {
      let tempArr = this.userData;

      tempArr = tempArr.filter((user) => {
        return user.favorites;
      });
      return tempArr;
    },

instead of filter?

No. With find() I meant to replace the

let idIndex = id - 1

part where you assume that the item with a specific id is always stored at index-position id - 1.
But maybe it is indexOf() rather than find, that I thought of as you dont want to find the item but rather it’s index-position.

1 Like

aha, I see. I will look in to that later, I have other things I need to fix first. I want thank you for your help! :smiley: