Home > Software design >  How to edit an array of objects in React?
How to edit an array of objects in React?

Time:11-22

Lets say I have a react state variable which is an array of user objects. The user object has 2 keys which are important to us: id and name. I have to change the name of 2 users in that list. The code for this is below

`

const App = () => {
  
  const [users, setUsers] = useState([]);
  
  useEffect(async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const users = await response.json();
    setUsers(users);
  }, [])
  
  const edit1 = (id) => {
    const newUsers = users.map(user => {
      if(user.id === id) {
        user.name = 'John Doe';
      } 
      return user;
    });    
    setUsers(newUsers);
  }
  
  const edit2 = (id) => {
    const newUsers = users.map(user => ({
      ...user,
      name: user.id === id ? 'Jane Doe' : user.name
    }));
    setUsers(newUsers);
  }
  
  return(
    <div>
      <button onClick={() => edit1(2)}>Edit-1</button>
      <button onClick={() => edit2(5)}>Edit-2</button>
      <ul>
        {users.map((user) => (<li key={user.key}>{user.name}</li>))}
      </ul>
    </div>
  );
}

`

Which approach is better? Between edit1 and edit2 which approach is better, since they both get the job done? Or is there another way to do this?

The problem is fairly straight forward. In edit1, new objects are not created, but a new array is created. Since React only checks to see if the reference to the array object has changed, which it has, it then goes on to rerender. In edit2, new objects are also created along with a new array. This causes a rerender as well. I'm confused as to which approach is better.

CodePudding user response:

Using edit2 you create a new array and new elements inside. But with edit1 even if you have a new array, the objects inside still have the refference to the old objects. So I suggest edit2.

CodePudding user response:

You should use the function-version of "setState". This grabs the current state so that you can set it without depending on the state itself. If you had this inside of useEffect, you could infinitely loop.

setUsers((existingUsers) =>
  existingUsers.map((user) => ({
    ...user,
    name: user.id === id ? "John Doe" : user.name // Apply change within spread
  }))
);

If you would like to avoid spreading; because this can become problematic with deeply-nested structures, you could use an immutable library such as Immer. Furthermore, Immer is used by Redux.

setUsers(
  produce((draftUsers) => {
    draftUsers.forEach((user) => {
      if (user.id === id) {
        user.name = "Jane Doe"; // Directly mutate the draft state
      }
    });
  })
);

Immer also has a useImmer hook that you can use to mutate your state. This removes the need to place the produce call in the middle of your state update.

For more info see: https://github.com/immerjs/use-immer

const [users, updateUsers] = useImmer([]); // use-immer hook

const edit3 = (id) => {
  updateUsers((draftUsers) => {
    draftUsers.forEach((user) => {
      if (user.id === id) {
        user.name = "Jane Doe";
      }
    });
  });
};

Working example

See CodeSandbox for a live version.

import { useEffect, useState } from "react";
import produce from "immer";

const App = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    (async () => {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      );
      const users = await response.json();
      setUsers(users);
    })();
  }, []);

  // Spread existing
  const edit1 = (id) => {
    setUsers((existingUsers) =>
      existingUsers.map((user) => ({
        ...user,
        name: user.id === id ? "John Doe" : user.name
      }))
    );
  };

  // Using Immer
  const edit2 = (id) => {
    setUsers(
      produce((draftUsers) => {
        draftUsers.forEach((user) => {
          if (user.id === id) {
            user.name = "Jane Doe";
          }
        });
      })
    );
  };

  return (
    <div>
      <button onClick={() => edit1(2)}>Edit-1</button>
      <button onClick={() => edit2(5)}>Edit-2</button>
      <ul>
        {users.map((user) => (
          <li key={user.key}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default App;
  • Related