Vue Warn : Duplicate keys detected: '1'. This may cause an update error

Hello Guys, I’m trying to render this data from JSON API. I have to make three calls:

My goal is to :

  • render a user list with some information (first call ) (I manage this)
  • but with the album of in each list in adequation of the id of each user id (second call)
  • and the same with another call (photos).
    For the moment, I just try to do it with the first and second call.

each of these data is an object in an array. So I copy each array in one in order to render what I want easily but It doesn’t work efficiently and I have this error in my console:

Vue Warn : Duplicate keys detected: ‘1’. This may cause an update error.
I read some stuff in the Vue.js documentation but I 'm still stuck. What should I do?

here my code :

my template :

`<div class="userInfos">
       <!-- through the merge/usersInfos array -->
        <ul v-for="user in usersInfos" :key="user.id">
          <!-- through the users/u array -->
          <li class="puce">{{user.name}}</li>
          <li>{{user.username}}</li>
          <li>{{user.email}}</li>
          <li>{{user.phone}}</li>
          <li>{{user.website}}</li>
          <li>{{user.title}}</li>
        </ul>        
      </div>

my app script :

mounted(){
    axios.all([
      axios.get('https://jsonplaceholder.typicode.com/users'), 
      axios.get('https://jsonplaceholder.typicode.com/users/1/albums'),
      axios.get('https://jsonplaceholder.typicode.com/users/1/photos')
    ])
    .then(axios.spread((users, albums, photos) => {
      this.usersInfos = [...users.data, ...albums.data];
        let usersInfo = this.usersInfos;

      console.log('users', users.data);
      console.log('albums', users.data);

       console.log('userInfo', usersInfo);

      console.log('photos', photos.data);
    }));

Thank you for your help !

The error is telling that there are duplicated keys.
You’re merging users.data and albums.data into one array. And that array looks something like this:

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    // ...some more fields
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    // ...some more fields
  },

  // ....more users data, and then albums data, like this
  {
    "userId": 1,
    "id": 1,
    title": "quidem molestiae enim"
  },
  {
    userId": 1,
    id": 2,
    title": "sunt qui excepturi placeat culpa"
  }
]

You can see that each id field is repeated twice. id: 1 and id: 2… and so on are appearing twice in the array, hence the error. IDs have to be unique.

On a side note, the resulting array contains inconsistent values so I’m not sure if you really want to merge these arrays in the first place.

1 Like

Thank you very much @husseyexplores! It makes sense of what I didn’t understand! Now, I need to figure out how to do what I want with this! Not easy part aha! If you have some ideas, please feel free!

As said, all the endpoints return objects with the same ids (starting at 1).

You can install uuid to generate unique ids. I guess you might be able to just use Math.random in a pinch.

:key="user.id + Math.random()"

Are you sure you want the usersInfos array to be like it is now?

I’m not sure I understand how you are planning to display the data. Right now you are only adding the user data to the view (but looping the albums as well).

What is the plan for the albums and photos? They will just come after the user data in the array. That doesn’t seem like a very useful data structure. If you want the albums and photos to be associated with each user you have to add the albums and photos data to each user object, not just add them to the end of the array.

1 Like

Thank you very much @lasjorg for your help! I appreciate that ! Indeed, I need clean code and you right, I think I have to add the albums and photos data to each user object. But I don’t know how to do that yet. I have to figure out.
The users story are:


Display the list of users (name, username, email, phone, website) – https://jsonplaceholder.typicode.com/users

In this list, be able to display the list of albums – https://jsonplaceholder.typicode.com/users/1/albums.

In this list, be able to display the list of photos (thumbnailUrl, title) – https://jsonplaceholder.typicode.com/users/1/photos

If a user’s albums or photos have already been uploaded, do not call the api again.

Power to filter by name or email – https://jsonplaceholder.typicode.com/users?username=XXXXXXXX


So here my code, if some of you @husseyexplores @lasjorg could give me some feedback about my code. Is it clean? It works but I have difficulties with the photos array because I have the same key property in the album array (title). So I’m wondering if I take the problem on the right side?
my template

<div class="userInfos">
       <!-- through the merge/usersInfos array -->
        <ul v-for="user in usersInfos" :key="user.id">
          <!-- through the users/u array -->
          <li class="puce"><strong>name:</strong> {{user.name}}</li>
          <li><strong>username:</strong> {{user.username}}</li>
          <li><strong>email:</strong> {{user.email}}</li>
          <li><strong>phone:</strong> {{user.phone}}</li>
          <li><strong>website:</strong> {{user.website}}</li>
          <p><strong>Albums:</strong></p>
          <li><strong>title:</strong> {{user.title}}</li>
          <p><strong>Photos:</strong></p>
          <li><strong>thumbnailUrl:</strong> {{user.thumbnailUrl}}</li>
          <li><strong>Title:</strong> {{user.Title}}</li>
        </ul>        
      </div>

here my script app :

mounted(){

    axios.all([

      axios.get('https://jsonplaceholder.typicode.com/users'), 

      axios.get('https://jsonplaceholder.typicode.com/users/1/albums'),

      axios.get('https://jsonplaceholder.typicode.com/users/1/photos')

    ])

    .then(axios.spread((users, albums, photos) => {

     

      let usersInfo = [...users.data, ...albums.data, ...photos.data];

      

      console.log('users', users.data);

      console.log('albums', albums.data);

      console.log('photos', photos.data);

    

      // Let's add each title in the right users'id object

      usersInfo.reduce((acc,item)=>{

        if(acc.filter((itemi) => itemi.id == item.id)[0]){

            acc.filter((itemi) => itemi.id == item.id)[0].title+=item.title;

            acc.filter((itemi) => itemi.id == item.id)[0].thumbnailUrl+=item.thumbnailUrl

            // acc.filter((itemi) => itemi.id == item.id)[0].title+=item.title

        }

        else acc.push(item);

        return acc

        }

      ,[]);

      // and here we put it off the id, title, and userId That we don't want to. 

      usersInfo.splice(10, 5010);

      console.log('userInfo', usersInfo);

      this.usersInfos = usersInfo;

      

    }));

Thank you again for your time!

“clean” code is a bit subjective to each developer’s preference in my opinion.

My question is, does your code work as expected? If it does not, then figure out the problem and its solution. Once you have it working, then try to refactor to make “clean” as you can.

I think that there are some logical errors in your code. For example:

You hit /users API endpoint and get a list of 10 users. And at the same time, you also hit /users/1/album and /users/1/photos endpoint, which gives you the albums and photos of user 1 (Only one user whos id is 1)


I’d personally do something like this:

  1. Fetch the list of users from /users endpoint and save it in your state.
  2. Render this users list (like you’re doing, but don’t include photos and albums since we have not fetched it yet)
  3. Have two buttons inside each rendered user item, which says something like, ‘View Albums’ and ‘View Photos’.
    Now, at this point, I’ve a list of 10 users rendered on the page with their details (username, email, phone, etc) and also two buttons for each user to fetch their albums and photos.
  4. Upon clicking any ‘View Albums’ or ‘View Photos’ button, you add click listener which will fetch the respective user’s photos/albums it inside the list, and remove that button since we have now fetched this data.

These are some raw steps that I though on the go. You’ll need to refine it quite a bit according to your user stories.

Just don’t think about making your code clean. First, make it work and understand how everything is going to work together step by step. It’ll make things much easier for you.

1 Like

Is there a reason that your :key wouldn’t just reference the index? If you’re having duplicates, it seems like the easiest solution.

<div v-for="(user, index) in userInfos" :key="index">
    . . .
</div>

I’m probably late to the party. @lasjorg had a great answer for generating a key as well.

1 Like

@husseyexplores Really thank you, man! I really appreciate that!

I started like that to be totally honest but I was stuck because I didn’t succeed to fetch the data with the button… :confused:
I’m trying to do it again with what you told me.
Thank you again :slight_smile:

1 Like

Thank you ! :slight_smile:

1 Like

@caleb-mabry You can definitely often get away with just using the index. But in general, it isn’t best practice because the array might change. For this list, it might be fine but it would be better not to rely on the index when possible.

I also believe there might be some differences between how Vue, and say, React technically handles lists.

reactjs: lists-and-keys, keys
vuejs: list, Maintaining-State

@Thibault-21 The endpoints are not really connected like how you might think. For example https://jsonplaceholder.typicode.com/albums and https://jsonplaceholder.typicode.com/users/1/albums return the same JSON. The same is true for the photos endpoint.

It really depends on what you are trying to do here and where you are going to display the data (in one place or several). It is possible to merge each user object with data from other endpoints if you do want that. But again there is nothing user specific in the other endpoints that ties the data to a user.

2 Likes

Thank you everybody for your help! I managed to do what I wanted, thank you :slight_smile: