nelkor
nelkor

Reputation: 99

Is this the correct way to update state?

My Component looks like this:

import cloneDeep from "clone-deep";
import { Context } from "../../context";

const Component = () => {
  const context = useContext(Context);
  const [state, setState] = useState(
    { 
      _id: "123", 
      users: [
        { 
          _id: "1",
          points: 5
        },
        {
          _id: "2",
          points: 8 
        }
      ]
    }
  );

  useEffect(() => {
    context.socket.emit("points");

    context.socket.on("points", (socketData) => {

          setState(prevState => {
    const newState = {...prevState};
    const index = newState.users
      .findIndex(user => user._id == socketData.content._id);
    newState.users[index].points = socketData.content.points;
    return newState;
  })
      
    });

    return () => context.socket.off("points");
  }, []);



  return <div>(There is table with identificators and points)</div>
};

I wonder if this is the right approach. I just want to write the code in the right way. Or maybe it's better with the use of deep cloning? Does it matter?

setState(prevState => {
const newState = cloneDeep(prevState);
const index = newState.users
  .findIndex(user => user._id == "2");
newState.users[index].points++;
return newState;

})

EDIT: I added the rest of the code to make it easier to understand.

Upvotes: 1

Views: 114

Answers (2)

gilamran
gilamran

Reputation: 7294

Your code will work, but the problem will start when your state becomes bigger. You currently have only two properties on the state, the _id and the users. If in the future will add more and more properties like loggedUser and settings and favorites, and more... your application will render everything on every state change.

At this point you will have to start thinking about other solutions to state management, like redux, mobx, or just split the state to smaller useState, also you can look into useReducer in complex structures.

Upvotes: 0

Rashomon
Rashomon

Reputation: 6762

In your current code:

  useEffect(() => {
    setState((prevState) => {
      const newState = { ...prevState };
      const index = newState.users.findIndex((user) => user._id == "2");
      newState.users[index].points++;
      console.log({ prevState, newState });
      return newState;
    });
  }, []);

You can see that prevState is being mutated (points is 9):

    { 
      _id: "123", 
      users: [
        { 
          _id: "1",
          points: 5
        },
        {
          _id: "2",
          points: 9 // Mutated!
        }
      ]
    }

To avoid mutating the state, you have to use not mutating methods such as spread operator or map function:

  useEffect(() => {
    setState((prevState) => {
      const newState = ({
        ...prevState,
        users: prevState.users.map((user) =>
          user._id === "2"
            ? {
                ...user,
                points: user.points + 1
              }
            : user
        )
      })
      console.log({ prevState, newState });
      return newState
      }
    );
  }, []);

Now you can see that the prevState is not mutated:

    { 
      _id: "123", 
      users: [
        { 
          _id: "1",
          points: 5
        },
        {
          _id: "2",
          points: 8 // Not mutated :)
        }
      ]
    }

Upvotes: 1

Related Questions