2

I have two local states, UserList and ModifiedUsers in my App function. The UserList is supposed to capture an array of user objects from an API, and ModifiedUsers will operate on a copy of the array of objects to reverse/sort the list.

const APIURL = "https://jsonplaceholder.typicode.com/users";
function App() {
  const [UserList, setUserList] = useState([]);
  let [ModifiedUsers, setModifiedUsers] = useState([]);

This getUsers method is used for fetching data from the API and storing it in a local variable users. Which I am then passing to both the UserList array and ModifiedUsers array.

const getUsers = async () => {
    let response = await fetch(APIURL);
    let users = await response.json();
    let modifiedUsers = users;
    setUserList(users);
    setModifiedUsers(modifiedUsers);
   };

This sortList method is called on a button click to sort the array. Even though in this method I am only updating the ModifiedUsers array, it is somehow updating the UserList array as well.

 const sortList = () => {
   
    let temp = 0;
    
    for (let i = 0; i < ModifiedUsers.length - 1; i++) {
      if (
        ModifiedUsers[i].name.trim().length >
        ModifiedUsers[i + 1].name.trim().length
      ) {
        temp = ModifiedUsers[i];
        ModifiedUsers[i] = ModifiedUsers[i + 1];
        ModifiedUsers[i + 1] = temp;
      }
    }
    setModifiedUsers(ModifiedUsers);
    if (click % 2 === 0) {
      ModifiedUsers.reverse();
      setModifiedUsers(ModifiedUsers);
    }


    setClick(++click);
     };

When I console log the UserList Array, I see that it has captured the modified values of ModifiedUsers array, even though I am nowhere updating the original UserList Array.

I am unable to retain the original array of objects no matter what I try. Please help.

Here is the code live on a codepen- https://codesandbox.io/s/api-forked-ie57j4?file=/src/App.js

I have tried creating multiple states, but all of them get updated similarly. I was expecting only the ModifedUsers array to get updated, but all of them did.

2 Answers2

2

Essentially, you are modifying your UserList — not only because it obviously behaves the same like your ModifiedUsers but because of the way you created it.

In JavaScript, all object variables (meaning everything except for primitives like string and boolean) are stored and copied by reference. This means: the value of users is only a pointer, pointing to the location of your actual array. When you copy this into your ModifiedUsers, you are only copying the pointer, which in turn leads to using the same array. And by sorting this one, the same change (hence the same array) is visible in UserList.

To modify the two arrays independently, you have to clone them, in order to create an exact copy with new pointers. For example, you can use the JavaScript spread syntax:

let modifiedUsers = [...users]

Keep in mind that this will only clone 1 level, so all the object pointers inside the array remain the same. (It only makes a shallow copy, as pointed out in the answer by @BikramKarki) If you need to change them independently as well, you have to deep clone your array. You can find some more inspiration on this here or on the web.

Janik
  • 688
  • 1
  • 6
  • 12
2

Addition to the answer by @Janik

This happens when the objects inside of an array or objects are still referencing the original object.

And we should be mindful of the use of the spread operator while copying an object or array. The spread operator only makes a copy of primitive data types and not objects (makes a shallow copy of the objects). Nested objects will still be referenced to the old object.

const personA = {
  name: "A",
  age: 5,
  address: {
    city: "City A",
    country: "Country A"
  }
}

const personB = {...personA};
personB.name = "B";
personB.address.city = "City B"

console.log(personA.name); // "A"
console.log(personA.address.city); // "City B" cause address object in "personB" is still referencing the address object in "personA"

Thus, the following is only make a shallow copy.

const modifiedUsers = [...users] // only makes shallow copy

So, we should instead make a deep copy of the objects/arrays with:

const modifiedUsers = JSON.parse(JSON.stringify(users));

NB: This method can only be used with serializable objects for eg.: if your objects or array has functions then it won't work.

More on Shallow vs Deep copy: https://www.freecodecamp.org/news/copying-stuff-in-javascript-how-to-differentiate-between-deep-and-shallow-copies-b6d8c1ef09cd/#:~:text=A%20deep%20copy%20means%20that,into%20how%20JavaScript%20stores%20values.

Bikram Karki
  • 985
  • 8
  • 8
  • Thank you, Bikram. I was scratching my head for this, it worked. Maybe that's why we ought to focus more on JS fundamentals before moving towards any JS Library/Framework. – Shree Nandan Das Apr 04 '22 at 04:15
  • You are absolutely right about this and I referenced your answer for further information on shallow copies. However, as long as only the array gets resorted/changed, without changing the properties of the objects inside, is a deep copy really necessary? – Janik Apr 04 '22 at 06:18
  • 1
    Shree, you are right about learning JS fundamentals and I also think it is part of the never-ending learning process, it would be nice to keep an eye open all the while. There are a lot of caveats that are not widely discussed along with the feature. In this case, the spread operator is a common method of choice to make a copy of an object or an array however, its limitation is not discussed as much. – Bikram Karki Apr 13 '22 at 11:31
  • Janik, what you said is correct. As long as you don't change the objects inside, it should work fine. – Bikram Karki Apr 13 '22 at 11:44